﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MacomberMapSystem.Common.Database;
using System.Windows.Forms;
using System.IO;
using System.Diagnostics;
using System.Xml;
using System.Drawing;
using System.Threading;
using MacomberMapSystem.Common.CIM;
using MacomberMapSystem.Common.Integration;
using MacomberMapSystem.Common.Internals;
using System.Drawing.Drawing2D;
using MacomberMapSystem.Common.Serialization;
using System.Reflection;
using VBRUN;
using MacomberMapSystem.Common.User_Interfaces.One_Lines;
using MacomberMapSystem.Common.Properties;
using System.Data.Common;
using System.Drawing.Imaging;
using System.Collections.Concurrent;

namespace MacomberMapSystem.Common.Exports
{
    /// <summary>
    /// This class is responsible for exporting a Macomber Map one-line out to DDL format
    /// </summary>
    public static class MM_DDL_Exporter
    {
        #region Variable declarations
        /// <summary>Our offset point for coordinates</summary>
        private static Point Offset = new Point(180, 0);

        /// <summary>Whether to use the old label method</summary>
        public static bool UseOldLabelMethod = false;
        #endregion

        #region One-line exporting
        /// <summary>Whether to skip the actual exporting</summary>
        public static bool SkipExport = false;

        /// <summary>
        /// Write and compile a one-line to ensure it functions properly.
        /// </summary>
        /// <param name="InLine"></param>
        /// <param name="CurModel"></param>
        /// <param name="DDLDirectory"></param>
        /// <param name="CompiledDirectory"></param>
        /// <param name="ImageDirectory"></param>
        /// <param name="EMSDirectory"></param>
        /// <param name="SubName"></param>
        /// <param name="ImageSource"></param>
        /// <param name="OneLineImage"></param>
        /// <param name="Repository"></param>
        /// <param name="Image"></param>
        /// <param name="NETMOM"></param>
        /// <param name="OTS"></param>
        /// <param name="SCADA"></param>        
        /// <param name="MMXmlDirectory"></param>
        /// <param name="OneLinesToCompile"></param>
        /// <returns></returns>                
        public static Exception WriteOneLine(MM_Database_OneLine InLine, MM_Database_Model CurModel, string DDLDirectory, String CompiledDirectory, String ImageDirectory, String EMSDirectory, string SubName, String ImageSource, Image OneLineImage, MM_Repository Repository, bool SCADA, bool NETMOM, bool OTS, bool Image, String MMXmlDirectory, ConcurrentQueue<MM_Database_OneLine> OneLinesToCompile, MM_Database_Assignment LastAssignment)
        {
            if (SkipExport)
                return null;

            //First, make sure our ESET is in the appropriate place
            if (!File.Exists(Path.Combine(DDLDirectory, "ercotonelines_eset.ddl")))
                using (StreamWriter sESET = new StreamWriter(Path.Combine(DDLDirectory, "ercotonelines_eset.ddl"), false, Encoding.ASCII))
                    sESET.Write(Data_Manager.WriteColorsAndModelSpecifics(Resources.ERCOTOneLines_ESet, CurModel));

            //Now, write out and compile our one-lines.
            try
            {


                foreach (String str in new string[] { DDLDirectory, CompiledDirectory, ImageDirectory, MMXmlDirectory })
                    if (!String.IsNullOrEmpty(str) && !Directory.Exists(str))
                        Directory.CreateDirectory(str);

                //Build our export info
                MM_Oneline_Export OLExport = CurModel.LocateExport(InLine);
                if (InLine.OneLineType == MM_Database_OneLine.OneLineTypeEnum.BreakerToBreaker)
                    SubName = "__" + SubName;
                else if (InLine.OneLineType == MM_Database_OneLine.OneLineTypeEnum.Operatorship)
                    SubName = "___" + SubName;


                //Produce all of our one-lines

                StreamWriter swSCADA = SCADA ? new StreamWriter(Path.Combine(DDLDirectory, "SCADA-" + SubName + "_dset.ddl"), false, new UTF8Encoding(false)) : null;
                StreamWriter swNETMOM = NETMOM ? new StreamWriter(Path.Combine(DDLDirectory, "NETMOM-" + SubName + "_dset.ddl"), false, new UTF8Encoding(false)) : null;
                StreamWriter swOTS = OTS ? new StreamWriter(Path.Combine(DDLDirectory, "OTS-" + SubName + "_dset.ddl"), false, new UTF8Encoding(false)) : null;

                MM_DDL_Exporter.ExportOneLine(InLine, CurModel, OneLineImage, OLExport, ImageDirectory, swSCADA, swNETMOM, swOTS, DDLDirectory, LastAssignment);
                if (swSCADA != null)
                    swSCADA.Dispose();
                if (swNETMOM != null)
                    swNETMOM.Dispose();
                if (swOTS != null)
                    swOTS.Dispose();


                //Export and compile 
                if (OneLinesToCompile != null)
                    OneLinesToCompile.Enqueue(InLine);
                else
                {
                    if (SCADA)
                        MM_DDL_Exporter.CompileOneLine(Path.Combine(DDLDirectory, "SCADA-" + SubName + "_dset.ddl"), CompiledDirectory, ImageSource, EMSDirectory);
                    if (NETMOM)
                        MM_DDL_Exporter.CompileOneLine(Path.Combine(DDLDirectory, "NETMOM-" + SubName + "_dset.ddl"), CompiledDirectory, ImageSource, EMSDirectory);
                    if (OTS)
                        MM_DDL_Exporter.CompileOneLine(Path.Combine(DDLDirectory, "OTS-" + SubName + "_dset.ddl"), CompiledDirectory, ImageSource, EMSDirectory);
                }

                //Save our image
                if (Image)
                    using (Bitmap OLImage = MM_Image_Exporter.ExportImage(InLine, CurModel, Path.Combine(ImageDirectory, SubName + ".png"), ImageFormat.Png, OLExport, LastAssignment))
                    using (DbCommand dCmd = Repository.Db.CreateCommand("UPDATE MM_DATABASE_ONELINE SET IMAGE=:0 WHERE ID=:1", OLImage, InLine.ID))
                        dCmd.ExecuteNonQuery();

                //Write out our XML
                if (!String.IsNullOrEmpty(MMXmlDirectory))
                    InLine.WriteOneLine(Path.Combine(MMXmlDirectory, SubName + ".MM_OneLine"), CurModel);


                //Read our log files
                if (OneLinesToCompile == null)
                {
                    StringBuilder OutSb = new StringBuilder();
                    FileInfo Ots = new FileInfo(Path.Combine(CompiledDirectory, "DISPLAY_SET_OTS-" + SubName + ".TXT"));
                    FileInfo Netmom = new FileInfo(Path.Combine(CompiledDirectory, "DISPLAY_SET_NETMOM-" + SubName + ".TXT"));
                    FileInfo Scada = new FileInfo(Path.Combine(CompiledDirectory, "DISPLAY_SET_SCADA-" + SubName + ".TXT"));
                    if (Ots.Exists)
                        using (StreamReader sRd = Ots.OpenText())
                            OutSb.AppendLine(sRd.ReadToEnd());
                    if (Netmom.Exists)
                        using (StreamReader sRd = Netmom.OpenText())
                            OutSb.AppendLine(sRd.ReadToEnd());
                    if (Scada.Exists)
                        using (StreamReader sRd = Scada.OpenText())
                            OutSb.AppendLine(sRd.ReadToEnd());



                    if (OutSb.ToString().Contains(" E:"))
                        return new Exception(OutSb.ToString());
                    else if (NETMOM && !File.Exists(CompiledDirectory + "\\RTNet\\" + SubName + ".fgdisplay"))
                        return new FileNotFoundException("Unable to locate RTNET One-Line");
                    else if (SCADA && !File.Exists(CompiledDirectory + "\\scada\\" + SubName + ".fgdisplay"))
                        return new FileNotFoundException("Unable to locate SCADA One-Line");
                    else if (OTS && !File.Exists(CompiledDirectory + "\\RTNet\\" + SubName + ".fgdisplay"))
                        return new FileNotFoundException("Unable to locate OTS One-Line");
                }
                return null;
            }
            catch (Exception ex)
            {
                return ex;
            }
        }

        /// <summary>
        /// Ensure our ESET is present, and if not, create it.
        /// </summary>
        /// <param name="DDLDirectory"></param>
        /// <param name="Model"></param>
        public static void WriteESET(String DDLDirectory, MM_Database_Model Model)
        {
            //Make sure our ESet is there already
            String ESet = Path.Combine(DDLDirectory, "ERCOTONELINES_ESET.DDL");
            if (!File.Exists(ESet))
                using (StreamWriter sESET = new StreamWriter(ESet, false, Encoding.ASCII))
                    sESET.Write(Data_Manager.WriteColorsAndModelSpecifics(MacomberMapSystem.Common.Properties.Resources.ERCOTOneLines_ESet, Model));

        }

        /// <summary>
        /// Export a one-line to a file on demand
        /// </summary>
        /// <param name="OneLine"></param>
        /// <param name="Model"></param>
        /// <param name="TargetFile"></param>
        /// <param name="SCADA"></param>
        /// <param name="OTS"></param>
        /// <param name="OneLineImage"></param>
        /// <param name="OLExport"></param>
        /// <param name="ImageDirectory"></param>            
        public static void ExportOneLine(MM_Database_OneLine OneLine, MM_Database_Model Model, String TargetFile, bool SCADA, bool OTS, Image OneLineImage, MM_Oneline_Export OLExport, String ImageDirectory, MM_Database_Assignment LastAssignment)
        {
            Offset.X = 30;
            Offset.Y = 30;

            //Then, write out our file            
            MM_DDL_Exporter_Stream OutStream;
            using (StringWriter sW = new StringWriter())
            {
                if (SCADA)
                    OutStream = new MM_DDL_Exporter_Stream(null, sW, null);
                else if (OTS)
                    OutStream = new MM_DDL_Exporter_Stream(null, null, sW);
                else
                    OutStream = new MM_DDL_Exporter_Stream(sW, null, null);
                OutStream.WriteHeader("WebFG 3.5.0, Builder 5.6.0");
                WriteDisplay(OutStream, OneLine, Model, OneLineImage, OLExport, ImageDirectory, Path.GetDirectoryName(TargetFile),LastAssignment);
                OutStream.Close();
                OutStream.Dispose();
                File.WriteAllText(TargetFile, sW.ToString());
            }
        }

        /// <summary>
        /// Export a one-line out to the specified path
        /// </summary>
        /// <param name="OneLine">The one-lines to be exported</param>
        /// <param name="Model">The model to be used</param>
        /// <param name="OneLineImage">The image of the one-line</param>
        /// <param name="OLExport">The one-line export/validation parameters</param>
        /// <param name="ImageDirectory"></param>
        /// <param name="swNETMOM"></param>
        /// <param name="swOTS"></param>
        /// <param name="swSCADA"></param>
        /// <param name="TargetDirectory"></param>
        public static void ExportOneLine(MM_Database_OneLine OneLine, MM_Database_Model Model, Image OneLineImage, MM_Oneline_Export OLExport, String ImageDirectory, TextWriter swSCADA, TextWriter swNETMOM, TextWriter swOTS, string TargetDirectory, MM_Database_Assignment LastAssignment)
        {
            Offset.X = 30;
            Offset.Y = 30;

            //Then, write out our file            
            MM_DDL_Exporter_Stream OutStream = new MM_DDL_Exporter_Stream(swNETMOM, swSCADA, swOTS);
            OutStream.WriteHeader("WebFG 3.5.0, Builder 5.6.0");
            WriteDisplay(OutStream, OneLine, Model, OneLineImage, OLExport, ImageDirectory, TargetDirectory,LastAssignment);
            OutStream.Close();            
        }

        /// <summary>
        /// Export one-lines out to a single file
        /// </summary>
        /// <param name="OneLines">The one-lines to be exported</param>
        /// <param name="Model">The model to be used</param>
        /// <param name="TargetFile">The target path name</param>
        /// <param name="Db">The database connector</param>
        /// <param name="ImageDirectory"></param>
        /// <param name="swNETMOM"></param>
        /// <param name="swOTS"></param>
        /// <param name="swSCADA"></param>
        public static void ExportOneLines(Dictionary<MM_Database_OneLine, MM_Oneline_Export> OneLines, MM_Database_Model Model, String TargetFile, MM_Database_Connector Db, String ImageDirectory, StringWriter swNETMOM, StringWriter swSCADA, StringWriter swOTS, MM_Database_Assignment LastAssignment)
        {
            if (!Model.EnsureOpened())
                return;

            //Then, write out our file
            String OutPath = Path.Combine(Path.GetDirectoryName(TargetFile), Path.GetFileNameWithoutExtension(TargetFile)).Replace("_dset", "").Replace("_eset", "") + "_dset.ddl";
            using (MM_DDL_Exporter_Stream OutStream = new MM_DDL_Exporter_Stream(swNETMOM, swSCADA, swOTS))
            {
                OutStream.WriteHeader("WebFG 3.5.0, Builder 5.6.0");
                foreach (KeyValuePair<MM_Database_OneLine, MM_Oneline_Export> OneLine in OneLines)
                {
                    WriteDisplay(OutStream, OneLine.Key, Model, Db.LoadOneLineImage(OneLine.Key.ID), OneLine.Value, ImageDirectory, Path.GetDirectoryName(TargetFile),LastAssignment);
                    OutStream.Close();
                }
            }
            Process.Start(OutPath);
        }


        /// <summary>
        /// Write a one-line display out
        /// </summary>
        /// <param name="OutStream">The outgoing stream</param>
        /// <param name="OneLine">The one-line to be exported</param>
        /// <param name="Model">The target model</param>
        /// <param name="OneLineImage">The one-line image</param>
        /// <param name="OLExport">The one-line export</param>        
        /// <param name="ImageDirectory"></param>
        /// <param name="TargetDirectory"></param>        
        public static void WriteDisplay(MM_DDL_Exporter_Stream OutStream, MM_Database_OneLine OneLine, MM_Database_Model Model, Image OneLineImage, MM_Oneline_Export OLExport, String ImageDirectory, String TargetDirectory, MM_Database_Assignment LastAssignment)
        {
            String BaseElementName = OLExport.BaseElement.TypeName == "cim:Company" ? OLExport.BaseElement["cim:IdentifiedObject.aliasName"] : OLExport.BaseElement.Name;

            //First, write out our header and map
            int MapTop;
            WriteHeader(OutStream, OneLine, Model, OLExport.BaseElement, BaseElementName, out MapTop, LastAssignment);
            WriteMap(OutStream, OneLine, Model, OLExport.BaseElement, MapTop);

            //Go through and export our images into a dset as needed
            List<Image> Images = new List<Image>();
            foreach (XmlElement xElem in OneLine.Document.SelectNodes("/One_Line/Unlinked_Elements/Image"))
                using (MemoryStream mS = new MemoryStream(Convert.FromBase64String(xElem.Attributes["Image"].Value)))
                    Images.Add(Image.FromStream(mS));

            if (Images.Count > 0)
            {
                //First, write out all of our images and our ddl
                using (StreamWriter sW = new StreamWriter(Path.Combine(TargetDirectory, "Images-" + BaseElementName + "_eset.ddl")))
                {
                    sW.WriteLine("\tversion (\"WebFG 3.5.0, Builder 5.6.0\");");
                    for (int Img = 0; Img < Images.Count; Img++)
                    {
                        sW.WriteLine("\tsymbol \"" + OLExport.BaseElement.Name + "-Img" + Img.ToString() + "\"");
                        sW.WriteLine("\t(");
                        sW.WriteLine("\t\trectangle");
                        sW.WriteLine("\t\t(");
                        sW.WriteLine("\t\tgab (");
                        sW.WriteLine("\t\t\t)");
                        sW.WriteLine("\t\torigin(0 0)");
                        sW.WriteLine("\t\twidth(" + Images[Img].Width.ToString() + ")");
                        sW.WriteLine("\t\theight(" + Images[Img].Height.ToString() + ")");
                        sW.WriteLine("\t\t)");
                        sW.WriteLine("\t\timage(\"" + OLExport.BaseElement.Name + "-Img" + Img.ToString() + ".jpg\")");
                        sW.WriteLine("\t\timage_attachment(none)");
                        sW.WriteLine("\t);");
                        using (Bitmap ImageToSave = new Bitmap(Images[Img]))
                        {
                            ImageToSave.Save(Path.Combine(TargetDirectory, OLExport.BaseElement.Name + "-Img" + Img.ToString() + ".jpg"), ImageFormat.Jpeg);
                            if (!String.IsNullOrEmpty(ImageDirectory))
                                ImageToSave.Save(Path.Combine(ImageDirectory, OLExport.BaseElement.Name + "-Img" + Img.ToString() + ".jpg"), ImageFormat.Jpeg);
                        }
                    }
                }
            }

            //Now, clear our our images
            foreach (Image img in Images)
                img.Dispose();
            Images.Clear();

            int CurImage = 0;
            //Now, write out our static components
            if (OneLine.Document.DocumentElement["Unlinked_Elements"] != null)
                foreach (XmlElement xElem in OneLine.Document.DocumentElement["Unlinked_Elements"].ChildNodes)
                {
                    Rectangle Bounds = MM_Serializable<Rectangle>.ConvertObject(xElem.Attributes["Bounds"].Value, null);
                    String ForeColor = "Gray";
                    String BackColor = "Black";
                    if (xElem.HasAttribute("ForeColor"))
                        ForeColor = xElem.Attributes["ForeColor"].Value;
                    if (xElem.HasAttribute("BackColor"))
                        BackColor = xElem.Attributes["BackColor"].Value;


                    if (xElem.Name == "Label")
                    {
                        Font WriteFont;
                        if (!xElem.HasAttribute("Font"))
                            WriteFont = new FontConverter().ConvertFromString("Microsoft Sans Serif, 8.25pt") as Font;
                        else
                            WriteFont = new FontConverter().ConvertFromString(xElem.Attributes["Font"].Value) as Font;
                        OutStream.WriteText(xElem.Attributes["Text"].Value, new Point(Bounds.Left + Offset.X, Bounds.Top + Offset.Y), "White", "Black", WriteFont.Name, (int)((float)WriteFont.Size * 1.5f), WriteFont.Underline, WriteFont.Bold, WriteFont.Italic, false, false);
                    }
                    else if (xElem.Name == "Rectangle")
                        OutStream.WriteRectangle(Bounds, Offset, ForeColor, BackColor);
                    else if (xElem.Name == "Circle")
                        OutStream.WriteEllipse(Bounds, Offset, ForeColor, BackColor);
                    else if (xElem.Name == "ArrowLine")
                        OutStream.WriteGraphicLine(Bounds.Location, new Point(Bounds.Bottom, Bounds.Right), true, "ARROW_FILLED");
                    else if (xElem.Name == "Image")
                        OutStream.WriteSymbol("IMAGES-" + BaseElementName.ToUpper() + "\\" + OLExport.BaseElement.Name + "-IMG" + (CurImage++).ToString(), Bounds.Location);
                }

            //Now, go through every element, and write our backgrounds for SPS/RAPs, and PUNs.
            foreach (KeyValuePair<XmlElement, Rectangle> kvp in OLExport.ElementBounds)
                if (!kvp.Key.Name.EndsWith("Descriptor"))
                {
                    CIM_Element Elem = OLExport.ElementMapping[kvp.Key.Name == "PokePoint" ? kvp.Key.ParentNode.ParentNode as XmlElement : kvp.Key];
                    CIM_Element PUN;
                    Model.PUNElements.TryGetValue(Elem.rdfID, out PUN);
                    bool IsRap = false, IsSps = false;
                    StringBuilder Comment = new StringBuilder();
                    foreach (CIM_Element RapSPS in Elem.SPSorRAPs)
                    {
                        if (XmlConvert.ToBoolean(RapSPS["IsRAP"]))
                            IsRap = true;
                        else if (XmlConvert.ToBoolean(RapSPS["IsRAS"]))
                            IsSps = true;
                        Comment.AppendLine(RapSPS.Name + " (" + RapSPS.LongName + ")");
                    }

                    if (PUN != null && (!kvp.Key.HasAttribute("IsVisible") || bool.Parse(kvp.Key.Attributes["IsVisible"].Value)))
                    {
                        OutStream.WriteFilledRectangleWithComment(new Rectangle(Offset.X + kvp.Value.Left - 10, Offset.Y + kvp.Value.Top - 10, kvp.Value.Width + 20, kvp.Value.Height + 20), Point.Empty, "DarkGray", "PUN " + PUN.Name);
                        //If we have a node included in a PUN, let's trace everything out from there
                        if (kvp.Key.Name == "Node")
                            foreach (XmlElement xTargetElem in kvp.Key.ChildNodes)
                                if (xTargetElem.Name != "Descriptor" && xTargetElem.Name != "SecondaryDescriptor")
                                {
                                    XmlElement TargetElem = kvp.Key.OwnerDocument.SelectSingleNode("//*[@BaseElement.TEID='" + xTargetElem.Attributes["TEID"].Value + "']") as XmlElement;
                                    Rectangle TargetRect = kvp.Value;
                                    Point Center1, Center2;
                                    if (Model.PUNElements.ContainsKey(OLExport.ElementMapping[TargetElem].rdfID))
                                    {
                                        foreach (XmlElement xNodeCh in xTargetElem.ChildNodes)
                                            if (xNodeCh.Name == "PokePoint" || xNodeCh.Name == "PricingVector")
                                            {
                                                Rectangle Bounds2 = MM_Serializable<Rectangle>.ConvertObject(xNodeCh.Attributes["Bounds"].Value, null);
                                                MM_DDL_Exporter.GetStraightLine(TargetRect, Bounds2, out Center1, out Center2, false, false);
                                                OutStream.WriteFilledRectangleWithComment(new Rectangle(Offset.X + Center1.X - 10, Offset.Y + Center1.Y - 10, Center2.X - Center1.X + 20, Center2.Y - Center1.Y + 20), Point.Empty, "DarkGray", "PUN " + PUN.Name);
                                                TargetRect = Bounds2;
                                            }
                                        MM_DDL_Exporter.GetStraightLine(TargetRect, MM_Serializable<Rectangle>.ConvertObject(TargetElem.Attributes["Bounds"].Value, null), out Center1, out Center2, false, false);
                                        OutStream.WriteFilledRectangleWithComment(new Rectangle(Offset.X + Center1.X - 10, Offset.Y + Center1.Y - 10, Center2.X - Center1.X + 20, Center2.Y - Center1.Y + 20), Point.Empty, "DarkGray", "PUN " + PUN.Name);
                                    }
                                }
                    }
                    if (kvp.Key.HasAttribute("IsVisible") && !bool.Parse(kvp.Key.Attributes["IsVisible"].Value))
                    { }
                    else if (IsRap && IsSps)
                    {
                        OutStream.WriteFilledRectangleWithComment(new Rectangle(Offset.X + kvp.Value.Left - 5, Offset.Y + kvp.Value.Top - 5, (kvp.Value.Height + 10) / 2, kvp.Value.Width + 10), Point.Empty, "DimGray", Comment.ToString());
                        OutStream.WriteFilledRectangleWithComment(new Rectangle(Offset.X + kvp.Value.Left - 5, Offset.Y + kvp.Value.Top + ((kvp.Value.Height + 10) / 2) - 5, (kvp.Value.Height + 10) / 2, kvp.Value.Width + 10), Point.Empty, "DarkOliveGreen", Comment.ToString());
                    }
                    else if (IsRap)
                        OutStream.WriteFilledRectangleWithComment(new Rectangle(Offset.X + kvp.Value.Left - 5, Offset.Y + kvp.Value.Top - 5, kvp.Value.Width + 10, kvp.Value.Height + 10), Point.Empty, "DimGray", Comment.ToString());
                    else if (IsSps)
                        OutStream.WriteFilledRectangleWithComment(new Rectangle(Offset.X + kvp.Value.Left - 5, Offset.Y + kvp.Value.Top - 5, kvp.Value.Width + 10, kvp.Value.Height + 10), Point.Empty, "DarkOliveGreen", Comment.ToString());
                }

            //Now, write out the paths between elements
            foreach (KeyValuePair<GraphicsPath, String> kvp in OLExport.NodePaths)
                foreach (KeyValuePair<PointF, PointF> kvp2 in MM_HitTesting.ExtractLines(kvp.Key))
                {
                    Point pt1 = Point.Round(new PointF(kvp2.Key.X + Offset.X, kvp2.Key.Y + Offset.Y));
                    Point pt2 = Point.Round(new PointF(kvp2.Value.X + Offset.X, kvp2.Value.Y + Offset.Y));
                    OutStream.WriteGraphicLine(pt1, pt2, false, kvp.Value.Replace(' ', '_'));
                }


            //Now, write out every element
            foreach (KeyValuePair<XmlElement, Rectangle> kvp in OLExport.ElementBounds)
                if (kvp.Key.Name == "SecondaryDescriptor")
                    if (OLExport.ElementMapping.ContainsKey(kvp.Key))
                        OutStream.WriteTextWithCam(OLExport.ElementMapping[kvp.Key].ParseSingle("etx:ElectricalBus<etx:ResourceNode", null).Name, "PV_SHOW_NETMOM", new Point(kvp.Value.X + Offset.X, kvp.Value.Y + Offset.Y), "LightGray", "Black", "Arial", 12, false, false, true, false, false);
                    else
                    {
                        if (OLExport.ElementMapping.ContainsKey(kvp.Key.ParentNode as XmlElement))
                        {
                            CIM_Element RN = OLExport.ElementMapping[kvp.Key.ParentNode as XmlElement].ParseSingle("etx:ElectricalBus<etx:ResourceNode", null);
                            if (RN != null)
                                OutStream.WriteTextWithCam(RN.Name, "PV_SHOW_NETMOM", new Point(kvp.Value.X + Offset.X, kvp.Value.Y + Offset.Y), "LightGray", "Black", "Arial", 12, false, false, true, false, false);
                        }
                        else
                            Console.WriteLine("hmmm...");
                    }

                else if (kvp.Key.Name == "Descriptor")
                { }
                else
                {
                    CIM_Element Elem = OLExport.ElementMapping[kvp.Key.Name == "PokePoint" ? kvp.Key.ParentNode.ParentNode as XmlElement : kvp.Key];
                    string KVLevel = "";
                    OLExport.KVLevels.TryGetValue(Elem, out KVLevel);
                    if (KVLevel == null)
                        KVLevel = "";
                    WriteElement(OutStream, OneLine, Model,  OLExport.BaseElement, kvp.Key, Model.Repository.Db.Synchroscopes, Model.Repository.Db.Synchrocheck_Relays, Elem, KVLevel, kvp.Value);
                }

            //Now, write out every label
            foreach (KeyValuePair<MM_OneLine_Value, XmlElement> OutPt in OLExport.OutValues)
                if (OutPt.Key.OutputMode == MM_OneLine_Value.enumOutputMode.Scada)
                    OutPt.Key.WriteDDL(OutStream, true, false, OutPt.Value.Attributes["Orientation"].Value.Substring(0, 1), OLExport.BaseElement, Offset, Model.Repository.Db.Synchroscopes, Model.Repository.Db.Synchrocheck_Relays);
                else if (OutPt.Key.ValueType == MM_OneLine_Value.enumValueType.FrequencyPanel)
                    OutPt.Key.WriteDDL(OutStream, false, true, OutPt.Value.Attributes["Orientation"].Value.Substring(0, 1), OLExport.BaseElement, Offset, Model.Repository.Db.Synchroscopes, Model.Repository.Db.Synchrocheck_Relays);
                else
                {
                    OutPt.Key.WriteDDL(OutStream, false, false, OutPt.Value.Attributes["Orientation"].Value.Substring(0, 1), OLExport.BaseElement, Offset, Model.Repository.Db.Synchroscopes, Model.Repository.Db.Synchrocheck_Relays);
                    OutPt.Key.WriteDDL(OutStream, false, true, OutPt.Value.Attributes["Orientation"].Value.Substring(0, 1), OLExport.BaseElement, Offset, Model.Repository.Db.Synchroscopes, Model.Repository.Db.Synchrocheck_Relays);
                }

            OutStream.WriteEndTag();
            OutStream.WriteEndTag();
        }
        #endregion

        #region One-line component exporting
        /// <summary>
        /// Write out our header information
        /// </summary>
        /// <param name="OutStream"></param>
        /// <param name="OneLine"></param>
        /// <param name="Model"></param>
        /// <param name="BaseElement">Our base element of this display</param>
        /// <param name="BaseElementName">The name of our base element</param>        
        /// <param name="MapTop">The top of the map</param>
        /// <param name="WriteRASPanel">Whether or not to write the RAS panel</param>
        private static void WriteHeader(MM_DDL_Exporter_Stream OutStream, MM_Database_OneLine OneLine, MM_Database_Model Model, CIM_Element BaseElement, String BaseElementName, out int MapTop, MM_Database_Assignment LastAssignment)
        {
            //Write the header information for our display
            if (BaseElement is CIM_Trace)
                OutStream.WriteStartTag("display", "__" + BaseElementName.ToUpper());
            else if (BaseElement.TypeName == "cim:Company")
                OutStream.WriteStartTag("display", "___" + BaseElementName.ToUpper());
            else
                OutStream.WriteStartTag("display", BaseElementName.ToUpper());

            //Write our title
            OutStream.WriteLine();
            OutStream.WriteAttribute("title", "localize", "%DIS%,%DISAPP%[%DISFAM%]    %HOST% (%VP%)    %REF%");


            //Write out our applications and high level data            
                OutStream.SCADA.WriteAttribute("application", "SCADA");            
                OutStream.OTS.WriteAttribute("application", "DTSPSM");            
                foreach (String str in "RTNET,SMTNET,STNET".Split(','))
                    OutStream.NETMOM.WriteAttribute("application", str);


            OutStream.WriteAttribute("color", "Black");
            OutStream.WriteAttribute("scale_to_fit_style(XY)");
            OutStream.WriteAttribute("pre_command", "DISPLAY/FIT");
            OutStream.WriteAttribute("post_command", "DISPLAY/NOFIT");
            OutStream.WriteStartTag("menu_bar_item", "ERCOT_DISPLAYS");
            OutStream.WriteAttribute("label", "localize", "Onelines");
            OutStream.WriteAttribute("set", "ERCOTONELINES");
            OutStream.WriteEndTag();

            //Determine the companies associated with operatorship on all items
            Dictionary<CIM_Element, String> Companies = new Dictionary<CIM_Element, string>();

           //using (StreamWriter sW2 = new StreamWriter(Path.Combine(Model.ModelFullPath, Model.Name + "-EquipOperatorship.csv"), true, Encoding.ASCII))
                foreach (CIM_Element Elem in BaseElement.Elements)
                    if (Elem.Operator != null)
                    {
                        String Alias = Elem.Operator.Attributes["cim:IdentifiedObject.aliasName"];
                        if (!Companies.ContainsKey(Elem.Operator))
                            Companies.Add(Elem.Operator, Alias);
                        /*if (Elem.TypeName == "cim:ACLineSegment")
                            sW2.WriteLine("{0},{1},{2},{3}", Elem.ElemType, "", Elem.LineName.ToUpper(), Alias);
                        else if (Elem.TypeName == "cim:SeriesCompensator")
                            sW2.WriteLine("{0},{1},{2},{3}", "SeriesCompensator", Elem.ACSegmentStations[0].Name, Elem.LineName.ToUpper(), Alias);
                        else if (Elem.TypeName == "cim:StaticVarCompensator" || Elem.TypeName == "cim:NonConformLoad" || Elem.TypeName == "cim:PowerTransformer" || Elem.TypeName == "cim:Disconnector" || Elem.TypeName == "cim:Breaker" || Elem.TypeName == "cim:SeriesCompensator" || Elem.TypeName == "cim:ShuntCompensator" || Elem.TypeName == "cim:TransformerWinding" || Elem.TypeName == "cim:CustomerLoad" || Elem.TypeName == "cim:SynchronousMachine")
                            sW2.WriteLine("{0},{1},{2},{3}", Elem.ElemType, Elem.Substation.Name, Elem.Name, Alias);
                        else if (Elem.TypeName == "etx:FrequencyRelay" || Elem.TypeName == "cim:GroundDisconnector" || Elem.TypeName == "cim:StationSupply" || Elem.TypeName == "etx:PricingVector" || Elem.TypeName == "etx:VoltageRelay" || Elem.TypeName == "cim:SynchrocheckRelay" || Elem.TypeName == "cim:Bay" || Elem.TypeName == "etx:EndCap" || Elem.TypeName == "cim:BusbarSection" || Elem.TypeName == "etx:VoltageMonitored" || Elem.TypeName == "etx:ConnectivityNodeGroup" || Elem.TypeName == "cim:ConnectivityNode")
                        { }*/
                    }
            OutStream.WriteStartTag("permitted_if", "");
            OutStream.WriteStartTag("one_of", "");

            //Write out our operatorship-based permissions
            foreach (KeyValuePair<CIM_Element, String> Elem in Companies)
                OutStream.WriteAttribute("class", Elem.Value);

            //Write out our company operatorship information for the substation
            /*if (BaseElement.ElemType == "Substation")
                using (StreamWriter sW2 = new StreamWriter(Path.Combine(Model.ModelFullPath, Model.Name + "-StOperatorship.csv"), true, Encoding.ASCII))
                    sW2.WriteLine(BaseElement.Name + "," + String.Join(",", Companies.Values.ToArray()));
            */
            OutStream.WriteAttribute("class", "ERCOT");
            OutStream.WriteEndTag();
            OutStream.WriteEndTag();

            OutStream.WriteAttribute("horizontal_unit", 10);
            OutStream.WriteAttribute("vertical_unit", 10);
            OutStream.WriteAttribute("horizontal_page", 50);
            OutStream.WriteAttribute("vertical_page", 50);
            OutStream.WriteAttribute("refresh", 2);
            OutStream.WriteAttribute("not locked_in_viewport");
            OutStream.WriteAttribute("horizontal_scroll_bar");
            OutStream.WriteAttribute("vertical_scroll_bar");
            OutStream.WriteAttribute("not std_menu_bar");
            OutStream.WriteAttribute("not command_window");
            OutStream.WriteAttribute("not on_top");
            OutStream.WriteAttribute("not ret_last_tab_pnum");
            OutStream.WriteAttribute("default_zoom(1.0000000)");
            OutStream.SCADA.WriteStartTag("simple_layer", "SCADA");
            OutStream.NETMOM.WriteStartTag("simple_layer", "NETWORK");
            OutStream.OTS.WriteStartTag("simple_layer", "NETWORK");
            

            //Now, write our header bar
            WriteHeaderBar(OutStream, OneLine, Model, BaseElement, BaseElementName, out MapTop,LastAssignment);
        }

        /// <summary>
        /// Return the keys for our data bound picture
        /// </summary>
        /// <param name="Elem"></param>
        /// <returns></returns>
        private static String[] GetRASKey(CIM_Element Elem)
        {
            CIM_Element Discrete = Elem.ParseSingle("DiscreteValue", null);
            CIM_Element DiscreteName = Discrete.ParseSingle("DiscreteName", null);
            CIM_Element Substation = Discrete.Substation;
            CIM_Element DeviceName = Discrete.ParentElement.ParentElement.ParentElement;
            String SubName = DeviceName.Substation.Name;
            String DeviceType = Discrete.ParseSingle("MeasurementGroup", null).ParentElement.ParseSingle("DeviceTypeName", null).Name;
            String Meas = Discrete.ParseSingle("MeasurementGroup", null).Name;
            return new string[] { "SUBSTN=" + SubName, "DEVTYP=" + DeviceType, "DEVICE=" + DeviceName.Name, "MEAS=" + Meas, "POINT=" + DiscreteName.Name };
        }

        /// <summary>
        /// Write out our RAS display
        /// </summary>
        /// <param name="OutStream"></param>
        /// <param name="OneLine"></param>
        /// <param name="Model"></param>
        /// <param name="BaseElement"></param>
        /// <param name="BaseElementName"></param>
        /// <param name="MapTop"></param>
        private static void WriteRASOneLinePanel(MM_DDL_Exporter_Stream OutStream, MM_Database_OneLine OneLine, MM_Database_Model Model, CIM_Element BaseElement, String BaseElementName, out int MapTop)
        {
            //Analyze to determine if we have any SPS measurements
            List<CIM_Element> Gates = new List<CIM_Element>();            
            CIM_Element OutputGate;
            foreach (CIM_Element Elem in BaseElement.Elements)
                foreach (CIM_Element SPS in Elem.SPSorRAPs)
                    foreach (CIM_Element Gate in SPS.Parse("TriggerCondition<Gate", null))
            
                        /*
                List<CIM_Element> GatesToCheck = new List<CIM_Element>();
                Gates.AddRange(Elem.Parse("cim:Terminal<Pin*>etx:Gate", Model.Repository));
                Gates.AddRange(                SPSElements.AddRange(this.Parse("<ProtAct*>PrivateProtectiveActions>StageTrigger>Stage>RemedialActionScheme", null));

                foreach (CIM_Element Gate in GatesToCheck)*/
                    try
                    {
                        String ShowTelem = Gate.ParseSingle("TriggerCondition>RemedialActionScheme", Model)["etx:RemedialActionScheme.showTelemOnDisplay"];
                        if (!Gates.Contains(Gate) && (String.IsNullOrEmpty(ShowTelem) || XmlConvert.ToBoolean(ShowTelem)))
                            if (Gate.ParseSingle("HasAMeasurement", null) != null)
                                Gates.Add(Gate);
                            else if ((OutputGate = Gate.ParseSingle("TriggerCondition>Gate", null)) != null && !Gates.Contains(OutputGate) && Gate.ParseSingle("TriggerCondition<Gate<HasAMeasurement", null) == null)
                                Gates.Add(OutputGate);
                    }
                    catch (Exception ex)
                    { }
                
            
            

            //If we have any gates, let's write out our one-line
            if (Gates.Count == 0)
                MapTop = 0;
            else
            {
                MapTop = (25 * Gates.Count) + 17;
                OutStream.WriteFilledRectangleWithComment(new Rectangle(0, 48, 160, (25 * Gates.Count) + 2), Point.Empty, "Black", "");
                List<String> RASNames = new List<string>();
                List<String> RASToolTips = new List<string>();
                foreach (CIM_Element Gate in Gates)
                {
                    CIM_Element CurrentRAS = Gate.ParseSingle("TriggerCondition>RemedialActionScheme", null);
                    if (!RASNames.Contains(CurrentRAS.Name))
                        RASNames.Add(CurrentRAS.Name);
                    if (!RASToolTips.Contains(CurrentRAS.Name + " (" + CurrentRAS.LongName + ")"))
                        RASToolTips.Add(CurrentRAS.Name + " (" + CurrentRAS.LongName + ")");
                }

                String SPSTitle = RASNames.Count == 0 ? "SPS: " + RASNames[0] : "SPSs: " + String.Join(", ", RASNames.ToArray());                                                
                OutStream.WriteTextWithGabAndToolTipAndKeySet(SPSTitle, "HYPERLINK2", new Point(4, 48),  String.Join("\r\n",RASToolTips.ToArray()), true, true, "SPS_MENU_NAVIGATION");
                OutStream.WriteRectangle(new Rectangle(0, 48, 9 * SPSTitle.Length, 15), Point.Empty, "White", "White", true, true);
                OutStream.WriteRectangle(new Rectangle(0, 63, 159, 25 * Gates.Count), Point.Empty, "White", "White", true, true);
                for (int Gate = 0; Gate < Gates.Count; Gate++)
                    WriteGate(Gates[Gate], OutStream, 8, 68 + (Gate * 22),true,true);
                
            }
        }


        /// <summary>
        /// Write and compile a RAS tabular display
        /// </summary>
        /// <param name="CurModel"></param>
        /// <param name="TargetFolder"></param>
        public static string[] WriteRASTabularDisplay(MM_Database_Model CurModel, String TargetFolder)
        {
            String TargetFile = Path.Combine(TargetFolder, "SPS_MENU_dset.ddl");
            using (StreamWriter OutSCADA = new  StreamWriter(TargetFile,false, new UTF8Encoding(false)))
            using (MM_DDL_Exporter_Stream OutStream = new MM_DDL_Exporter_Stream(null, OutSCADA, null))
            {
                OutStream.WriteHeader("WebFG 3.5.0, Builder 5.6.0");
                OutStream.WriteStartTag("display", "SPS_MENU");

                //Write our title
                OutStream.WriteLine();
                OutStream.WriteAttribute("title", "localize", "%DIS%,%DISAPP%[%DISFAM%]    %HOST% (%VP%)    %REF%");


                //Write out our applications and high level data            
                OutStream.WriteAttribute("application", "SCADA");
                OutStream.WriteAttribute("color", "Black");
                OutStream.WriteAttribute("scale_to_fit_style(XY)");
                OutStream.WriteAttribute("pre_command", "DISPLAY/FIT");
                OutStream.WriteAttribute("post_command", "DISPLAY/NOFIT");
                OutStream.WriteStartTag("menu_bar_item", "ERCOT_DISPLAYS");
                OutStream.WriteAttribute("label", "localize", "Onelines");
                OutStream.WriteAttribute("set", "ERCOTONELINES");
                OutStream.WriteEndTag();
                OutStream.WriteAttribute("horizontal_unit", 10);
                OutStream.WriteAttribute("vertical_unit", 10);
                OutStream.WriteAttribute("horizontal_page", 50);
                OutStream.WriteAttribute("vertical_page", 50);
                OutStream.WriteAttribute("refresh", 2);
                OutStream.WriteAttribute("not locked_in_viewport");
                OutStream.WriteAttribute("horizontal_scroll_bar");
                OutStream.WriteAttribute("vertical_scroll_bar");
                OutStream.WriteAttribute("not std_menu_bar");
                OutStream.WriteAttribute("not command_window");
                OutStream.WriteAttribute("not on_top");
                OutStream.WriteAttribute("not ret_last_tab_pnum");
                OutStream.WriteAttribute("default_zoom(1.0000000)");
                OutStream.WriteStartTag("simple_layer", "SCADA");

                SortedDictionary<String, CIM_Element> RASs = new SortedDictionary<string, CIM_Element>();
                foreach (CIM_Element RAS in CurModel.ElementsOfType("etx:RemedialActionScheme"))
                    if (XmlConvert.ToBoolean(RAS.Attributes["etx:RemedialActionScheme.isRAS"]))
                        RASs.Add(RAS.Name, RAS);

                OutStream.WriteFilledRectangleWithComment(new Rectangle(0, 0, 180, 20), Point.Empty, "MediumAquamarine", "SCADA SPS Display, Auto-generated by NMMS Post Processor");
                OutStream.WriteText("SPS Display", new Point(44,1), "White", "Black", "Arial", 14, true, true, false, false,false);
                int CurY = 20;
                foreach (CIM_Element RAS in RASs.Values)
                {

                    //Determine our gates
                    List<CIM_Element> Gates = new List<CIM_Element>();
                    String ShowTelem = RAS.Attributes["etx:RemedialActionScheme.showTelemOnDisplay"];
                    if (String.IsNullOrEmpty(ShowTelem) || XmlConvert.ToBoolean(ShowTelem))
                    {
                        foreach (CIM_Element Gate in RAS.Parse("TriggerCondition<Gate", null))
                            if (Gate.ParseSingle("HasAMeasurement", null) != null)
                                Gates.Add(Gate);
                        if (Gates.Count == 0)
                            Gates.Add(RAS.ParseSingle("TriggerCondition>Gate", null));
                    }

                    //Now, write our rectangle and gates out
                    OutStream.WriteRectangle(new Rectangle(0, CurY, 180, 15 + (Gates.Count * 15)), Point.Empty, "White", "White", false,false);
                    OutStream.WriteTextWithGabAndToolTip(RAS.Name, "TEXT_FV_NUM_NONENTRY_SIMPLE_ERCOT_IT", new Point(4, CurY), RAS.LongName, false,false);
                    CurY += 15;
                    foreach (CIM_Element Gate in Gates)
                    {
                        WriteGate(Gate, OutStream, 24, CurY, false,false);
                        CurY += 15;
                    }
                }

                OutStream.WriteEndTag();
                OutSCADA.WriteLine(");");
            }
            return new string[] {TargetFile};
        }

        /// <summary>
        /// Write out a gate for our element
        /// </summary>
        /// <param name="Gate"></param>
        /// <param name="OutStream"></param>
        /// <param name="X"></param>
        /// <param name="Y"></param>
        /// <param name="XLocked"></param>
        /// <param name="YLocked"></param>
        private static void WriteGate(CIM_Element Gate, MM_DDL_Exporter_Stream OutStream, int X, int Y, bool XLocked, bool YLocked)
        {
            //Write the name of our gate
            CIM_Element AssocElem = Gate.ParseSingle("<Pin*>Terminal", null);
            CIM_Element RASElem = Gate.ParseSingle("TriggerCondition>RemedialActionScheme", null);



            if (AssocElem != null)
                if (XLocked)
                    OutStream.WriteTextWithGabAndToolTip(AssocElem.Elements[0].LineName, "TEXT_FV_NUM_NONENTRY_SIMPLE_ERCOT_IT", new Point(X, Y), RASElem.Name + " / " + RASElem.LongName, XLocked, YLocked);
                else if (AssocElem.Elements[0].AssociatedTraces.Length > 0)
                    OutStream.WriteTextWithGabAndToolTipAndKeySet(AssocElem.Elements[0].LineName, "HYPERLINK2", new Point(X, Y), RASElem.Name + " / " + RASElem.LongName, XLocked, YLocked, "NAV_B2B_" + AssocElem.Elements[0].AssociatedTraces[0].Name.ToUpper());
                else
                    OutStream.WriteTextWithGabAndToolTip(AssocElem.Elements[0].LineName, "TEXT_FV_NUM_NONENTRY_SIMPLE_ERCOT_IT", new Point(X, Y), RASElem.Name + " / " + RASElem.LongName, XLocked, YLocked);
            else if (XLocked)
                OutStream.WriteTextWithGabAndToolTip(RASElem.Name, "TEXT_FV_NUM_NONENTRY_SIMPLE_ERCOT_IT", new Point(X, Y), RASElem.Name + " / " + RASElem.LongName, XLocked, YLocked);
            else
            {
                Dictionary<CIM_Element, int> SubstationTally = new Dictionary<CIM_Element, int>();
                foreach (CIM_Element Elem in RASElem.Elements)
                    foreach (CIM_Element Sub in Elem.Substation == null ? Elem.ACSegmentStations : new CIM_Element[] { Elem.Substation })
                        if (Sub != null)
                            if (SubstationTally.ContainsKey(Sub))
                                SubstationTally[Sub]++;
                            else
                                SubstationTally.Add(Sub, 1);
                int MaxCount = 0;
                foreach (int Tally in SubstationTally.Values)
                    MaxCount = Math.Max(MaxCount, Tally);
                foreach (KeyValuePair<CIM_Element, int> kvp in SubstationTally)
                    if (kvp.Value == MaxCount)
                    {
                        OutStream.WriteTextWithGabAndToolTipAndKeySet(RASElem.Name, "HYPERLINK2", new Point(X, Y), RASElem.Name + " / " + RASElem.LongName, XLocked, YLocked, "NAV_SUB_" + kvp.Key.Name.ToUpper());
                        break;
                    }
            }

            //Write out the measurements
            List<CIM_Element> ScadaPoints = new List<CIM_Element>(Gate.Parse("etx:HasAMeasurement", null));
            if (ScadaPoints.Count == 0)
                foreach (CIM_Element GateToAdd in Gate.Parse("TriggerCondition>RemedialActionScheme<HasAMeasurement", null))
                    if (!ScadaPoints.Contains(GateToAdd))
                        ScadaPoints.Add(GateToAdd);

            foreach (CIM_Element ScadaPoint in ScadaPoints)
            {
                String ID = ScadaPoint.Attributes["etx:HasAMeasurement.measurementType"];
                ID = ID.Substring(ID.LastIndexOf('.') + 1);
                if (ID == "status")
                    OutStream.WriteDataBoundPictureWithToolTip("RAS_LIGHTBULB", "Status\r\n\r\nGreen - In Service\r\nYellow - Suspect\r\nGray - Unavailable\r\nRed - Out of Service", new Point(X + 87, Y + 1), GetRASKey(ScadaPoint), XLocked, YLocked);
                else if (ID == "armed")
                    OutStream.WriteDataBoundPictureWithToolTip("RAS_LIGHTBULB_REVERSED", "Armed\r\n\r\nGreen - Disarmed\r\nYellow - Suspect\r\nGray - Unavailable\r\nRed - Armed", new Point(X + 103, Y + 1), GetRASKey(ScadaPoint), XLocked, YLocked);
                else if (ID == "tripped")
                    OutStream.WriteDataBoundPictureWithToolTip("RAS_LIGHTBULB_REVERSED", "Tripped\r\n\r\nGreen - Not Tripped\r\nYellow - Suspect\r\nGray - Unavailable\r\nRed - Tripped", new Point(X + 119, Y + 1), GetRASKey(ScadaPoint), XLocked, YLocked);
                else if (ID == "communication")
                    OutStream.WriteDataBoundPictureWithToolTip("RAS_LIGHTBULB_REVERSED", "Communication\r\n\r\nGreen - Online\r\nYellow - Suspect\r\nGray - Unavailable\r\nRed - Offline", new Point(X + 135, Y + 1), GetRASKey(ScadaPoint), XLocked, YLocked);
            }
        }
            

        /// <summary>
        /// Write out the header bar
        /// </summary>
        /// <param name="OutStream"></param>
        /// <param name="OneLine"></param>
        /// <param name="Model"></param>
        /// <param name="BaseElement"></param>
        /// <param name="BaseElementName"></param>
        /// <param name="MapTop"></param>
        private static void WriteHeaderBar(MM_DDL_Exporter_Stream OutStream, MM_Database_OneLine OneLine, MM_Database_Model Model, CIM_Element BaseElement, String BaseElementName, out int MapTop, MM_Database_Assignment LastAssignment)
        {
            String LongName;
            //int ViewBarWidth = 194; //141
            if (BaseElement is CIM_Trace)
                LongName = (BaseElement as CIM_Trace).Description;
            else
                LongName = MM_Type_Converter.TitleCase(BaseElement.LongName); //OneLine.Document.DocumentElement.Attributes["BaseElement.LongName"].Value;

            String Name = MM_Type_Converter.TitleCase(BaseElementName); // OneLine.Document.DocumentElement.Attributes["BaseElement.Name"].Value;

            //Find the person who last had an assignment
            String ExportedOn = "Exported on: " + DateTime.Now.ToShortDateString() + (LastAssignment == null ? "" : " (on " + LastAssignment.CompletedOn.ToShortDateString() + " by " + LastAssignment.CompletedBy.FullName.Substring(0,1) + LastAssignment.CompletedBy.FullName.Split(' ')[1].Substring(0,1)+")");
            String CIMFile = "CIM file: " + Model.Name;

            int ImageWidth = 0;
            foreach (XmlNode xNode in OneLine.Document.SelectNodes("//@Bounds"))
            {
                Rectangle Parse = MM_Serializable<Rectangle>.ConvertObject(xNode.Value, null);
                ImageWidth = Math.Max(ImageWidth, Parse.Right);
            }

            //Write our RAS one-line panel            
            WriteRASOneLinePanel(OutStream, OneLine, Model, BaseElement, BaseElementName, out MapTop);

           


            //Determine the positions for our components
            Point StationPoint = new Point(470, 8);

            Point NetmomStudy, NetmomSCADAExports;
            String OutText = (String.IsNullOrEmpty(LongName) || LongName.Equals(Name, StringComparison.CurrentCultureIgnoreCase)) ? Name : LongName + " (" + Name + ")";

            using (Graphics g = Graphics.FromHwnd(IntPtr.Zero))
            using (Font ArialFont = new Font("Arial", 20 * 3 / 4, FontStyle.Underline))
            {
                int TitleWidth = Size.Ceiling(g.MeasureString(OutText, ArialFont)).Width;
                NetmomStudy = new Point(StationPoint.X + TitleWidth + 20, 3);
                NetmomSCADAExports = new Point(NetmomStudy.X + 206, 3);                
                using (Font DrawFont = new Font("Arial", 12 * 3 / 4, FontStyle.Underline))
                {
                    ImageWidth = Math.Max(ImageWidth, NetmomSCADAExports.X + (int)Math.Ceiling(g.MeasureString(ExportedOn, DrawFont).Width));
                    ImageWidth = Math.Max(ImageWidth, NetmomSCADAExports.X + (int)Math.Ceiling(g.MeasureString(CIMFile, DrawFont).Width));
                }
            }

            //Write our hidden substation information
            if (BaseElement.ElemType != "Substation")
            { }
            else
            {
                OutStream.SCADA.WriteStartTag("picture", "INVISIBLE_STATION_SCADA");
                OutStream.SCADA.WriteAttribute("set", "ERCOTONELINES");
                OutStream.SCADA.WriteAttribute("origin", StationPoint.X, StationPoint.Y);
                OutStream.SCADA.WriteAttribute("xlocked");
                OutStream.SCADA.WriteAttribute("ylocked");
                OutStream.SCADA.WriteKey("SUBSTN=" + BaseElement.Name);
                OutStream.SCADA.WriteEndTag();

                OutStream.NETMOM.WriteStartTag("picture", "INVISIBLE_STATION_NETMOM");
                OutStream.NETMOM.WriteAttribute("set", "ERCOTONELINES");
                OutStream.NETMOM.WriteAttribute("cam", "NETAPP_TITLE_BACKGROUND");
                OutStream.NETMOM.WriteAttribute("set", "ERCOTONELINES");
                OutStream.NETMOM.WriteAttribute("origin", StationPoint.X, StationPoint.Y);
                OutStream.NETMOM.WriteAttribute("xlocked");
                OutStream.NETMOM.WriteAttribute("ylocked");
                OutStream.NETMOM.WriteKey("ST=" + BaseElement.Name);
                OutStream.NETMOM.WriteEndTag();

                OutStream.OTS.WriteStartTag("picture", "INVISIBLE_STATION_NETMOM");
                OutStream.OTS.WriteAttribute("set", "ERCOTONELINES");
                OutStream.OTS.WriteAttribute("cam", "NETAPP_TITLE_BACKGROUND");
                OutStream.OTS.WriteAttribute("set", "ERCOTONELINES");
                OutStream.OTS.WriteAttribute("origin", StationPoint.X, StationPoint.Y);
                OutStream.OTS.WriteAttribute("xlocked");
                OutStream.OTS.WriteAttribute("ylocked");
                OutStream.OTS.WriteKey("ST=" + BaseElement.Name);
                OutStream.OTS.WriteEndTag();

            }


            //Write the bar on top
            OutStream.WriteStartTag("rectangle", "");
            //String Background = SCADA ? "MediumAquamarine" : "SteelBlue";
            OutStream.SCADA.WriteStartTag("gab", "");
            OutStream.SCADA.WriteAttribute("foreground", "MediumAquamarine");
            OutStream.SCADA.WriteAttribute("fill");
            OutStream.SCADA.WriteEndTag();

            OutStream.NETMOM.WriteAttribute("cam", "NETAPP_TITLE_BACKGROUND");
            OutStream.NETMOM.WriteAttribute("set", "ERCOTONELINES");
            OutStream.NETMOM.WriteAttribute("gab", "PRIM_ROW_BACKGROUND");
            OutStream.NETMOM.WriteAttribute("set", "ERCOTONELINES");

            OutStream.OTS.WriteAttribute("cam", "NETAPP_TITLE_BACKGROUND");
            OutStream.OTS.WriteAttribute("set", "ERCOTONELINES");
            OutStream.OTS.WriteAttribute("gab", "PRIM_ROW_BACKGROUND");
            OutStream.OTS.WriteAttribute("set", "ERCOTONELINES");

            OutStream.WriteAttribute("xlocked");
            OutStream.WriteAttribute("ylocked");
            OutStream.WriteAttribute("origin", 0, 0);
            OutStream.WriteAttribute("width", ImageWidth + Offset.X);
            OutStream.WriteAttribute("height", 37);
            OutStream.WriteEndTag();

            //Write our titles
            OutStream.SCADA.WriteText(ExportedOn, NetmomSCADAExports, "White", "MediumAquamarine", "Arial", 12, false, false, false, true, true);
            OutStream.SCADA.WriteText(CIMFile, new Point(NetmomSCADAExports.X, 21), "White", "MediumAquamarine", "Arial", 12, false, false, false, true, true);
            OutStream.NETMOM.WriteText(ExportedOn, NetmomSCADAExports, "White", "SteelBlue", "Arial", 12, false, false, false, true, true);
            OutStream.NETMOM.WriteText(CIMFile, new Point(NetmomSCADAExports.X, 21), "White", "SteelBlue", "Arial", 12, false, false, false, true, true);
            OutStream.OTS.WriteText(ExportedOn, NetmomSCADAExports, "White", "SteelBlue", "Arial", 12, false, false, false, true, true);
            OutStream.OTS.WriteText(CIMFile, new Point(NetmomSCADAExports.X, 21), "White", "SteelBlue", "Arial", 12, false, false, false, true, true);


            //Write our 'go to' and app button
            OutStream.SCADA.WriteTextWithGab("SCADA", "TEXT_TITLE_SIMPLE", new Point(120, 5), true, true);


            //Write our button out twice
            foreach (bool DTS in new bool[] { true, false })
            {
                OutStream.SCADA.WriteStartTag("symbol", "BTN_POKE_6CHAR_NARROW");
                OutStream.SCADA.WriteAttribute("set", "ERCOTONELINES");
                OutStream.SCADA.WriteAttribute("keyset \"MENU\"");
                OutStream.SCADA.WriteAttribute("set", "ERCOTONELINES");
                OutStream.SCADA.WriteAttribute("cam \"SCADAMOM_IS_DTSPSM_" + (DTS ? "VISIBLE" : "INVISIBLE") + "\"");
                OutStream.SCADA.WriteAttribute("set", "ERCOTONELINES");
                OutStream.SCADA.WriteAttribute("menu \"GOTO_ONELINES_" + (DTS ? "WITHDTS\"" : "NOTDTS\""));
                OutStream.SCADA.WriteAttribute("set", "ERCOTONELINES");
                OutStream.SCADA.WriteAttribute("origin", 4, 5);
                OutStream.SCADA.WriteAttribute("xlocked");
                OutStream.SCADA.WriteAttribute("ylocked");
                OutStream.SCADA.WriteEndTag();
            }
            OutStream.SCADA.WriteTextWithGab("Go To", "TEXT_TITLE_COLUMN_HEADERS", new Point(34, 7), true, true);
            OutStream.SCADA.WriteStartTag("symbol", "MENU_PULLDOWN_ICON");
            OutStream.SCADA.WriteAttribute("set", "ERCOTONELINES");
            OutStream.SCADA.WriteAttribute("origin", 13, 7);
            OutStream.SCADA.WriteAttribute("xlocked");
            OutStream.SCADA.WriteAttribute("ylocked");
            OutStream.SCADA.WriteEndTag();

            OutStream.SCADA.WriteStartTag("picture", "SCADA_HEADER");
            OutStream.SCADA.WriteAttribute("set", "ERCOTONELINES");
            OutStream.SCADA.WriteAttribute("origin", 225, 4);
            OutStream.SCADA.WriteAttribute("xlocked");
            OutStream.SCADA.WriteAttribute("ylocked");
            OutStream.SCADA.WriteEndTag();

            //Output SCADA legend Key to the left of the "Exported On:" Label
            OutStream.SCADA.WriteStartTag("picture", "ONELINE-LEGEND");
            OutStream.SCADA.WriteAttribute("set", "ERCOTONELINES");
            OutStream.SCADA.WriteAttribute("origin", NetmomSCADAExports.X - 30, 15);
            OutStream.SCADA.WriteAttribute("xlocked");
            OutStream.SCADA.WriteAttribute("ylocked");
            OutStream.SCADA.WriteEndTag();

            //Now, write our OTS and NETMOM one-lines  

            //Output OTS Study/Run Buttons  
            OutStream.OTS.WriteStartTag("picture", "NETAPP_FILTERBUTTONS");
            OutStream.OTS.WriteAttribute("set", "ERCOTONELINES");
            OutStream.OTS.WriteAttribute("origin", NetmomStudy.X - 24, NetmomStudy.Y);
            OutStream.OTS.WriteAttribute("xlocked");
            OutStream.OTS.WriteAttribute("ylocked");
            OutStream.OTS.WriteEndTag();

            OutStream.OTS.WriteStartTag("picture", "NETAPP_HEADER");
            OutStream.OTS.WriteAttribute("set", "ERCOTONELINES");
            OutStream.OTS.WriteAttribute("origin", 0, 0);
            OutStream.OTS.WriteAttribute("xlocked");
            OutStream.OTS.WriteAttribute("ylocked");
            OutStream.OTS.WriteEndTag();

            //Output OTS legend Key between the Study/Run Buttons and "Exported On:" Label
            OutStream.OTS.WriteStartTag("picture", "ONELINE-LEGEND");
            OutStream.OTS.WriteAttribute("set", "ERCOTONELINES");
            OutStream.OTS.WriteAttribute("origin", NetmomSCADAExports.X - 30, 15);
            OutStream.OTS.WriteAttribute("xlocked");
            OutStream.OTS.WriteAttribute("ylocked");
            OutStream.OTS.WriteEndTag();

            //Output NETMOM Study/Run Buttons
            OutStream.NETMOM.WriteStartTag("picture", "NETAPP_FILTERBUTTONS");
            OutStream.NETMOM.WriteAttribute("set", "ERCOTONELINES");
            OutStream.NETMOM.WriteAttribute("origin", NetmomStudy.X - 24, NetmomStudy.Y);
            OutStream.NETMOM.WriteAttribute("xlocked");
            OutStream.NETMOM.WriteAttribute("ylocked");
            OutStream.NETMOM.WriteEndTag();

            OutStream.NETMOM.WriteStartTag("picture", "NETAPP_HEADER");
            OutStream.NETMOM.WriteAttribute("set", "ERCOTONELINES");
            OutStream.NETMOM.WriteAttribute("origin", 0, 0);
            OutStream.NETMOM.WriteAttribute("xlocked");
            OutStream.NETMOM.WriteAttribute("ylocked");
            OutStream.NETMOM.WriteEndTag();

            //Output NETMOM Legend Key between the Study/Run Buttons and "Exported On:" Label
            OutStream.NETMOM.WriteStartTag("picture", "ONELINE-LEGEND");
            OutStream.NETMOM.WriteAttribute("set", "ERCOTONELINES");
            OutStream.NETMOM.WriteAttribute("origin", NetmomSCADAExports.X - 30, 15);
            OutStream.NETMOM.WriteAttribute("xlocked");
            OutStream.NETMOM.WriteAttribute("ylocked");
            OutStream.NETMOM.WriteEndTag();


            /*OutStream.WriteText("Exported on: " + DateTime.Now.ToString(), new Point(4, 5), "White", Background, "Arial", 12, false, false, false, true,true );
            OutStream.WriteText("CIM file: " + Model.Name, new Point(4, 23), "White", Background, "Arial", 12, false, false, false, true, true);                                   
            Single Latitude = XmlConvert.ToSingle(OneLine.Document.DocumentElement.Attributes["BaseElement.Latitude"].Value);
            Single Longitude = XmlConvert.ToSingle(OneLine.Document.DocumentElement.Attributes["BaseElement.Longitude"].Value);
            String County = OneLine.Document.DocumentElement.HasAttribute("BaseElement.County") ? OneLine.Document.DocumentElement.Attributes["BaseElement.County"].Value : "Unknown";
            OutStream.WriteText("Coordinates: " + Latitude.ToString("#.000") + ", " + Longitude.ToString("#.000"), new Point(OneLineImage.Width, 5), "White", Background, "Arial", 12, false, false, false, true, true);
            OutStream.WriteText("County: " + (BaseElement.County == null ? County : BaseElement.County.Name), new Point(OneLineImage.Width, 23), "White", Background, "Arial", 12, false, false, false, true, true);
            */

            //Write out substation info
            OutStream.WriteText(OutText, StationPoint, "White", "Black", "Arial", 20, true, false, false, true, true);
        }

        /// <summary>
        /// Return the angle between this substation and another, in radians
        /// </summary>
        /// <param name="Pt1"></param>
        /// <param name="Pt2"></param>
        /// <returns>Angle, in radians</returns>
        public static float AngleTo(PointF Pt1, PointF Pt2)
        {
            float DeltaX = Pt2.X - Pt1.X;
            float DeltaY = Pt2.Y - Pt1.Y;
            return (float)Math.Atan2(DeltaY, DeltaX);
        }


        /// <summary>
        /// Return the distance between this substation and another, in miles
        /// </summary>
        /// <param name="Pt1"></param>
        /// <param name="Pt2"></param>
        /// <returns>Distance, in miles.</returns>
        public static float DistanceTo(PointF Pt1, PointF Pt2)
        {
            float R = 3963.1f; //Radius of the earth in miles
            float deltaLat = (Pt2.Y - Pt1.Y) * (float)Math.PI / 180f;
            float deltaLon = (Pt2.X - Pt1.X) * (float)Math.PI / 180f;
            float a = (float)Math.Sin(deltaLat / 2f) * (float)Math.Sin(deltaLat / 2f) + (float)Math.Cos(Pt1.Y * (float)Math.PI / 180f) * (float)Math.Cos(Pt2.Y * Math.PI / 180d) * (float)Math.Sin(deltaLon / 2f) * (float)Math.Sin(deltaLon / 2f);
            float c = 2f * (float)Math.Atan2(Math.Sqrt(a), Math.Sqrt(1f - a));
            return R * c;
        }

        /// <summary>
        /// Write out our map on the display
        /// </summary>
        /// <param name="OutStream"></param>
        /// <param name="OneLine"></param>
        /// <param name="Model"></param>
        /// <param name="BaseElement">The base element</param>        
        public static void WriteMap(MM_DDL_Exporter_Stream OutStream, MM_Database_OneLine OneLine, MM_Database_Model Model, CIM_Element BaseElement, int MapTop)
        {
            //Start off by building our list of all substation coordinates
            Dictionary<CIM_Element, PointF> ConnectedStations = new Dictionary<CIM_Element, PointF>();
            if (BaseElement.TypeName == "cim:Substation")
                ConnectedStations.Add(BaseElement, Model.GetCoordinates(BaseElement));                            
            foreach (CIM_Element ConnectedStation in BaseElement.ConnectedStations)
                ConnectedStations.Add(ConnectedStation, Model.GetCoordinates(ConnectedStation));


            //Determine our positional boundaries
            PointF Min = new PointF(float.NaN, float.NaN), Max = new PointF(float.NaN, float.NaN);
            foreach (PointF pt in ConnectedStations.Values)
            {
                if (float.IsNaN(Min.X) || Min.X > pt.X)
                    Min.X = pt.X;
                if (float.IsNaN(Max.X) || Max.X < pt.X)
                    Max.X = pt.X;
                if (float.IsNaN(Min.Y) || Min.Y > pt.Y)
                    Min.Y = pt.Y;
                if (float.IsNaN(Max.Y) || Max.Y < pt.Y)
                    Max.Y = pt.Y;
            }
            PointF Delta = new PointF(Max.X - Min.X, Max.Y - Min.Y);
            PointF Center = new PointF((Min.X + Max.X) / 2f, (Max.Y + Max.Y) / 2f);

            //First, place our elements
            OutStream.WriteStartTag("picture", "MAP_PLACEHOLDER");
            OutStream.WriteAttribute("set", "ERCOTONELINES");
            OutStream.WriteAttribute("cam", "MAP_SHOW_NETMOM");
            OutStream.WriteAttribute("set", "ERCOTONELINES");
            OutStream.WriteAttribute("origin", 0, 51+MapTop);
            OutStream.WriteAttribute("xlocked");
            OutStream.WriteAttribute("ylocked");
            OutStream.WriteEndTag();


            //Determine our distance between all of our lines, and build our list of lines            
                
            float MileDistance = float.NaN;
            Dictionary<CIM_Element, Point[]> LineCoordinates = new Dictionary<CIM_Element, Point[]>();
            Rectangle FirstBox = new Rectangle(20, 68 + MapTop, 140, 140);
            Rectangle SecondBox = new Rectangle(0, 232 + MapTop, 180, 180);


            foreach (CIM_Element ConnLine in BaseElement.ConnectedLines)
            {
                float LineDistance = MM_DDL_Exporter.DistanceTo(ConnectedStations[ConnLine.ACSegmentStations[0]], ConnectedStations[ConnLine.ACSegmentStations[1]]);
                if (float.IsNaN(MileDistance) || MileDistance < LineDistance)
                    MileDistance = LineDistance;
                PointF FromStation = ConnectedStations[ConnLine.ACSegmentStations[0]];
                PointF ToStation = ConnectedStations[ConnLine.ACSegmentStations[1]];
                if (!float.IsNaN(FromStation.X) && !float.IsNaN(ToStation.X))
                    LineCoordinates.Add(ConnLine, new Point[] { FitPoint(FromStation, FirstBox, Min, Max), FitPoint(ToStation, FirstBox, Min, Max), FitPoint(FromStation, SecondBox, BaseElement.CIM.Repository.Db.StateBoundary.Min, BaseElement.CIM.Repository.Db.StateBoundary.Max), FitPoint(ToStation, SecondBox, BaseElement.CIM.Repository.Db.StateBoundary.Min, BaseElement.CIM.Repository.Db.StateBoundary.Max) });
            }

            //Augment our mile distance for improved readability
            //if (!float.IsNaN(MileDistance))
            //    MileDistance *= 1.2f;


            //Now, write out our information for each line
            Point CenterPoint = new Point(90, 90 + MapTop);
            foreach (KeyValuePair<CIM_Element, Point[]> kvp in LineCoordinates)
            {
                OutStream.WriteStartTag("polyline", "");
                MM_KVLevel KVLevel = Data_Manager.LocateKVLevel(kvp.Key.VoltageLevel);
                OutStream.WriteAttribute("gab \"" + KVLevel.Name.Replace(' ', '_').ToUpper() + "\"");
                OutStream.WriteAttribute("set", "ERCOTONELINES");
                OutStream.WriteAttribute("cam", "MAP_SHOW_NETMOM");
                OutStream.WriteAttribute("set", "ERCOTONELINES");
                OutStream.WriteAttribute("origin", kvp.Value[0].X, kvp.Value[0].Y);
                OutStream.WriteAttribute("xlocked");
                OutStream.WriteAttribute("ylocked");
                OutStream.WriteAttribute("point", kvp.Value[0].X, kvp.Value[0].Y);
                OutStream.WriteAttribute("point", kvp.Value[1].X - kvp.Value[0].X, kvp.Value[1].Y - kvp.Value[0].Y);
                OutStream.WriteEndTag();

                //Now, also write out our label for any substation 
                CIM_Element OtherStation;
                for (int a = 0; a < kvp.Key.ACSegmentStations.Length; a++)
                    if (!(OtherStation = kvp.Key.ACSegmentStations[a]).Equals(BaseElement))
                    {
                        OutStream.SCADA.WriteStartTag("picture", "HYPERLINK-SCADA");
                        OutStream.NETMOM.WriteStartTag("picture", "HYPERLINK-NETMOM");
                        OutStream.OTS.WriteStartTag("picture", "HYPERLINK-NETMOM");
                        OutStream.WriteAttribute("set", "ERCOTONELINES");
                        OutStream.WriteAttribute("cam", "MAP_SHOW_NETMOM");
                        OutStream.WriteAttribute("set", "ERCOTONELINES");

                        if (String.IsNullOrEmpty(OtherStation.LongName) || OtherStation.LongName == OtherStation.Name)
                            OutStream.WriteAttribute("tool_tip(localize \"Navigate to " + OtherStation.Name + "\" )");
                        else
                            OutStream.WriteAttribute("tool_tip(localize \"Navigate to " + OtherStation.LongName + " (" + OtherStation.Name + ")\" )");
                        OutStream.WriteAttribute("origin", kvp.Value[a].X, kvp.Value[a].Y);

                        OutStream.WriteAttribute("xlocked");
                        OutStream.WriteAttribute("ylocked");
                        OutStream.SCADA.WriteKey("SUBSTN=" + OtherStation.Name);
                        OutStream.NETMOM.WriteKey("ST=" + OtherStation.Name);
                        OutStream.OTS.WriteKey("ST=" + OtherStation.Name);
                        OutStream.WriteEndTag();
                    }
            }






            //Now, write our legend                
            OutStream.WriteStartTag("polyline", "");
            OutStream.WriteAttribute("gab \"TEXTCOLOR\"");
            OutStream.WriteAttribute("set", "ERCOTONELINES");
            OutStream.WriteAttribute("cam", "MAP_SHOW_NETMOM");
            OutStream.WriteAttribute("set", "ERCOTONELINES");
            OutStream.WriteAttribute("origin", 2, 38 + MapTop);
            OutStream.WriteAttribute("xlocked");
            OutStream.WriteAttribute("ylocked");
            OutStream.WriteAttribute("point", 3, 64+MapTop);
            OutStream.WriteAttribute("point", 0, -5);
            OutStream.WriteAttribute("point", 46, 0);
            OutStream.WriteAttribute("point", 0, 5);
            OutStream.WriteEndTag();

            OutStream.WriteStartTag("text", "");
            OutStream.WriteAttribute("gab \"SMALLTEXTCOLOR\"");
            OutStream.WriteAttribute("set", "ERCOTONELINES");
            OutStream.WriteAttribute("cam", "MAP_SHOW_NETMOM");
            OutStream.WriteAttribute("set", "ERCOTONELINES");
            OutStream.WriteAttribute("origin", 7, 70 + MapTop);
            OutStream.WriteAttribute("xlocked");
            OutStream.WriteAttribute("ylocked");
            OutStream.WriteAttribute("localize \"" + MileDistance.ToString("0.0") + " mi\"");
            OutStream.WriteEndTag();


            //Now, write our lines
            Point MinMap = new Point(int.MaxValue, int.MaxValue), MaxMap = new Point(int.MinValue, int.MinValue);
            foreach (KeyValuePair<CIM_Element, Point[]> kvp in LineCoordinates)
            {
                OutStream.WriteStartTag("polyline", "");
                OutStream.WriteAttribute("gab \"" + Data_Manager.LocateKVLevel(kvp.Key.VoltageLevel).Name.Replace(' ', '_').ToUpper() + "\"");
                OutStream.WriteAttribute("set", "ERCOTONELINES");
                OutStream.WriteAttribute("cam", "MAP_SHOW_NETMOM");
                OutStream.WriteAttribute("set", "ERCOTONELINES");
                OutStream.WriteAttribute("origin", kvp.Value[2].X, kvp.Value[2].Y);
                OutStream.WriteAttribute("xlocked");
                OutStream.WriteAttribute("ylocked");
                OutStream.WriteAttribute("point", kvp.Value[2].X, kvp.Value[2].Y);
                OutStream.WriteAttribute("point", kvp.Value[3].X - kvp.Value[2].X, kvp.Value[3].Y - kvp.Value[2].Y);
                OutStream.WriteEndTag();


                MinMap.X = Math.Min(MinMap.X, Math.Min(kvp.Value[2].X, kvp.Value[3].X));
                MinMap.Y = Math.Min(MinMap.Y, Math.Min(kvp.Value[2].Y, kvp.Value[3].Y));
                MaxMap.X = Math.Max(MaxMap.X, Math.Max(kvp.Value[2].X, kvp.Value[3].X));
                MaxMap.Y = Math.Max(MaxMap.Y, Math.Max(kvp.Value[2].Y, kvp.Value[3].Y));
            }

            //Draw the boundaries around everything
            MinMap.X -= 2;
            MinMap.Y -= 2;
            MaxMap.X += 2;
            MaxMap.Y += 2;

    
            //Draw our rectangle around our point
            if (LineCoordinates.Count > 0)
            {
                OutStream.WriteStartTag("rectangle", "");
                OutStream.WriteAttribute("gab \"TEXTCOLOR\"");
                OutStream.WriteAttribute("set", "ERCOTONELINES");
                OutStream.WriteAttribute("cam", "MAP_SHOW_NETMOM");
                OutStream.WriteAttribute("set", "ERCOTONELINES");
                OutStream.WriteAttribute("origin", MinMap.X, MinMap.Y);
                OutStream.WriteAttribute("xlocked");
                OutStream.WriteAttribute("ylocked");
                OutStream.WriteAttribute("width", MaxMap.X - MinMap.X);
                OutStream.WriteAttribute("height", MaxMap.Y - MinMap.Y);
                OutStream.WriteEndTag();


                //Now, draw our cross-hairs            
                Point CenterMap = MM_Converter.CenterRect(Rectangle.FromLTRB(MinMap.X, MinMap.Y, MaxMap.X, MaxMap.Y));
                for (int a = 0; a < 4; a++)
                {
                    OutStream.WriteStartTag("polyline", "");
                    OutStream.WriteAttribute("gab \"TEXTCOLOR\"");
                    OutStream.WriteAttribute("set", "ERCOTONELINES");
                    OutStream.WriteAttribute("cam", "MAP_SHOW_NETMOM");
                    OutStream.WriteAttribute("set", "ERCOTONELINES");
                    Point From, To;
                    if (a == 0)
                    {

                        From = new Point(0, CenterMap.Y);
                        To = new Point(MinMap.X, CenterMap.Y);
                    }
                    else if (a == 1)
                    {
                        From = new Point(MaxMap.X, CenterMap.Y);
                        To = new Point(180, CenterMap.Y);
                    }
                    else if (a == 2)
                    {
                        From = new Point(CenterMap.X, 231 + MapTop);
                        To = new Point(CenterMap.X, MinMap.Y);
                    }
                    else
                    {
                        From = new Point(CenterMap.X, MaxMap.Y);
                        To = new Point(CenterMap.X, 231 + 180+MapTop);
                    }
                    OutStream.WriteAttribute("origin", From.X, From.Y);
                    OutStream.WriteAttribute("xlocked");
                    OutStream.WriteAttribute("ylocked");
                    OutStream.WriteAttribute("point", From.X, From.Y);
                    OutStream.WriteAttribute("point", To.X - From.X, To.Y - From.Y);
                    OutStream.WriteEndTag();
                }
            }

            //Now, write out our control panel
            StringBuilder cPanelParameters = new StringBuilder();
            cPanelParameters.AppendLine("Lines=" + CheckType(OneLine, "Line"));
            cPanelParameters.AppendLine("Transformers=" + CheckType(OneLine, "Transformer"));
            cPanelParameters.AppendLine("Units=" + CheckType(OneLine, "Unit"));
            cPanelParameters.AppendLine("Loads=" + CheckType(OneLine, "Load", "LAAR"));
            cPanelParameters.AppendLine("Shunts=" + CheckType(OneLine, "Capacitor", "Reactor"));
            cPanelParameters.AppendLine("Breakers/Switches=" + CheckType(OneLine, "Breaker", "Switch"));
            cPanelParameters.Append("Nodes=" + CheckType(OneLine, "Node"));

            //WriteControl(OutStream, new Rectangle(4, 404, 185, 329), "ERCOTControls.ctlControlPanel", "", cPanelParameters);    */
        }

        /// <summary>
        /// Convert a point to a specified rectangle
        /// </summary>
        /// <param name="LatLng"></param>
        /// <param name="DisplayRectangle"></param>
        /// <param name="TopLeft"></param>
        /// <param name="BottomRight"></param>
        /// <returns></returns>
        private static Point FitPoint(PointF LatLng, Rectangle DisplayRectangle, PointF TopLeft, PointF BottomRight)
        {
            float DeltaX = (BottomRight.X - TopLeft.X);
            if (DeltaX == 0f)
                DeltaX = 0.001f;
            float DeltaY = (TopLeft.Y - BottomRight.Y);
            if (DeltaY == 0f)
                DeltaY = 0.001f;
            return Point.Round(new PointF(
                    (((float)DisplayRectangle.Width * (LatLng.X - TopLeft.X) / DeltaX) + (float)DisplayRectangle.Left),
                    (((float)DisplayRectangle.Height * (LatLng.Y - BottomRight.Y) / DeltaY)+ (float)DisplayRectangle.Top)));     
        }

        /// <summary>
        /// Write out our control panel
        /// </summary>
        /// <param name="OutStream"></param>
        /// <param name="BoundingRectangle"></param>
        /// <param name="ControlType"></param>
        /// <param name="Parameters"></param>
        /// <param name="Query"></param>
        private static void WriteControl(MM_DDL_Exporter_Stream OutStream, Rectangle BoundingRectangle, string ControlType, string Query, StringBuilder Parameters)
        {
            OutStream.WriteStartTag("control", "");
            OutStream.WriteAttribute("origin", BoundingRectangle.Left, BoundingRectangle.Top);
            OutStream.WriteAttribute("width", BoundingRectangle.Width);
            OutStream.WriteAttribute("height", BoundingRectangle.Height);
            OutStream.WriteAttribute("xlocked");
            OutStream.WriteAttribute("ylocked");
            OutStream.WriteAttribute("progid", ControlType);
            
            PropertyBag pBag = new PropertyBag();
            pBag.WriteProperty("Query", Query.ToString(), "");
            pBag.WriteProperty("Parameters", Parameters.ToString(), "");


            byte[] OutBytes = (byte[])pBag.Contents;
            List<String> OutAttributes = new List<string>();
            int CurCol = 0;
            StringBuilder OutSb = new StringBuilder(OutBytes.Length.ToString() + " ");
            foreach (byte OutByte in OutBytes)
            {
                OutSb.Append(OutByte.ToString("X") + " ");
                CurCol += OutByte.ToString("X").Length;
                if (CurCol > 238)
                {
                    CurCol = 0;
                    OutSb.AppendLine();
                }
            }

            //Now, write each row out
            String InLine;
            using (StringReader sRd = new StringReader(OutSb.ToString()))
                while (!String.IsNullOrEmpty(InLine = sRd.ReadLine()))
                    OutStream.WriteAttribute("ctrldata", InLine);
            OutStream.WriteEndTag();
        }

        /// <summary>
        /// Determine whether one or more types are present in our document
        /// </summary>
        /// <param name="OneLine">The one-line to search</param>
        /// <param name="ElemTypes">The element types to look for</param>
        /// <returns></returns>
        private static bool CheckType(MM_Database_OneLine OneLine, params string[] ElemTypes)
        {
            foreach (string str in ElemTypes)
                if (OneLine.Document.SelectNodes("//" + str).Count > 0)
                    return true;
            return false;
        }

      
        /// <summary>
        /// Locate all analogs associated with SCADA that are connected to the specified substation
        /// </summary>
        /// <param name="BaseElement">The element to parse</param>
        /// <param name="SubstationElement">The required substatoin</param>
        /// <returns></returns>
        private static Dictionary<String, CIM_Element> LoadAnalogs(CIM_Element BaseElement, CIM_Element SubstationElement)
        {
            Dictionary<String, CIM_Element> Analogs = new Dictionary<string, CIM_Element>();
            foreach (CIM_Element Analog in BaseElement.Parse("cim:Analog", null))
                if (!XmlConvert.ToBoolean(Analog["etx:Analog.forNetwork"]) && (Analog.ParseSingle("etx:PseudoTerminal",null) != null || Analog.ParseSingle("cim:Terminal", null).Substation.Equals(SubstationElement)))
                    if (!Analogs.ContainsKey(Analog.Name))
                    Analogs.Add(Analog.Name, Analog);                                                                       
            return Analogs;
        }

        /// <summary>
        /// Write out the image for an element
        /// </summary>
        /// <param name="OutStream"></param>
        /// <param name="OneLine"></param>
        /// <param name="Model"></param>
        /// <param name="BaseElement"></param>
        /// <param name="xElem"></param>
        /// <param name="Synchroscopes"></param>
        /// <param name="KVLevel"></param>
        /// <param name="Elem"></param>
        /// <param name="Bounds"></param>
        /// <param name="Synchocheck_Relays"></param>        
        private static void WriteElement(MM_DDL_Exporter_Stream OutStream, MM_Database_OneLine OneLine, MM_Database_Model Model, CIM_Element BaseElement, XmlElement xElem, Dictionary<CIM_RdfID, bool> Synchroscopes, Dictionary<CIM_RdfID, bool> Synchocheck_Relays, CIM_Element Elem, string KVLevel, Rectangle Bounds)
        {
           
            String SubName = Elem.Substation == null ? "" : Elem.Substation.Name;
            String ElemName = Elem.Name;
            String PopupText = Elem.GetPopupText();

            //Rectangle Bounds = MM_Serializable<Rectangle>.ConvertObject(xElem.Attributes["Bounds"].Value, null);
            //CIM_Element Elem = Model.FromRdfID(xElem.Attributes["rdfID"].Value);
            //String KVLevel = Elem.VoltageLevel == null ? "OTHERKV" : Data_Manager.LocateKVLevel(Elem.VoltageLevel.Name).Name.Replace(" ","").ToUpper();

            if (xElem.Name == "Breaker" || xElem.Name == "Switch")
            {
                String Orientation = xElem.Attributes["Orientation"].Value;
                String NormalOpenSuffix = "";// XmlConvert.ToBoolean(Elem["cim:Switch.normalOpen"]) ? "_NO" : "";
                Orientation = (Orientation == "Vertical" || Orientation == "Up" || Orientation == "Down") ? "V" : "H";

                //Write out our SCADA values

                CIM_Element Discrete = null;
                CIM_Element DiscreteName = null;
                foreach (CIM_Element DiscToTest in Elem.Parse("Discrete<DiscreteValue", null))
                    if ((DiscreteName = DiscToTest.ParseSingle("DiscreteName", null)) != null && DiscreteName.Name == "ST")
                        Discrete = DiscToTest;
                if (Discrete == null)
                    OutStream.WriteSymbolWithToolTip("CB-UNKNOWN", new Point(Offset.X + Bounds.Left + 1, Offset.Y + Bounds.Top + 1), PopupText);
                else
                {
                    CIM_Element Meas = Discrete.ParseSingle("MeasurementGroup", null);
                    if (Meas == null)
                        Meas = Discrete.ParseSingle("Calculation", null);
                    OutStream.SCADA.WriteDataBoundPictureWithToolTip((xElem.Name == "Breaker" ? "CB_" : "SW_") + KVLevel.Replace(" ", "") + "_" + Orientation + NormalOpenSuffix, PopupText, new Point(Offset.X + Bounds.Left + 1, Offset.Y + Bounds.Top + 1), "SUBSTN=" + SubName, (xElem.Name == "Breaker" ? "DEVTYP=CB" : "DEVTYP=DSC"), "DEVICE=" + ElemName, "MEAS=" + Meas.Name, "POINT=" + Discrete.ParseSingle("DiscreteName", null).Name);
                }
                OutStream.NETMOM.WriteDataBoundPictureWithToolTip((xElem.Name == "Breaker" ? "CB_NET_" : "SW_NET_") + KVLevel.Replace(" ", "") + "_" + Orientation + NormalOpenSuffix, PopupText, new Point(Offset.X + Bounds.Left + 1, Offset.Y + Bounds.Top + 1), "ST=" + SubName, (xElem.Name == "Breaker" ? "CBTYP=CB" : "CBTYP=DSC"), "CB=" + ElemName);
                OutStream.OTS.WriteDataBoundPictureWithToolTip((xElem.Name == "Breaker" ? "CB_NET_" : "SW_NET_") + KVLevel.Replace(" ", "") + "_" + Orientation + NormalOpenSuffix, PopupText, new Point(Offset.X + Bounds.Left + 1, Offset.Y + Bounds.Top + 1), "ST=" + SubName, (xElem.Name == "Breaker" ? "CBTYP=CB" : "CBTYP=DSC"), "CB=" + ElemName);
                if (XmlConvert.ToBoolean(Elem["cim:Switch.normalOpen"]))
                    OutStream.WriteText("N.O.", new Point(Offset.X + Bounds.Left + (Orientation == "V" ? 32 : 26), Offset.Y + Bounds.Top + (Orientation == "V" ? 23 : 15)), "Cyan", "Black", "Arial", 8, false, false, false, false, false);
            }
            else if (xElem.Name == "Reactor" || xElem.Name == "Resistor")
            {
                String Orientation = xElem.Attributes["Orientation"].Value;
                Orientation = (Orientation == "Vertical" || Orientation == "Up" || Orientation == "Down") ? "V" : "H";
                OutStream.WriteSymbolWithToolTip(xElem.Name.ToUpper() + "_" + KVLevel.Replace(" ", "") + "_" + Orientation, new Point(Bounds.Left + 1 + Offset.X, Offset.Y + Bounds.Top + 1), PopupText);
            }

            else if (xElem.Name == "ResistorReactor")
            {
                String Orientation = xElem.Attributes["Orientation"].Value;
                Orientation = (Orientation == "Vertical" || Orientation == "Up" || Orientation == "Down") ? "V" : "H";
                if (Orientation == "V")
                {
                    OutStream.WriteSymbolWithToolTip("REACTOR_" + KVLevel.Replace(" ", "") + "_" + Orientation, new Point(Bounds.Left + Offset.X, Bounds.Top + Offset.Y + 1), PopupText);
                    OutStream.WriteSymbolWithToolTip("RESISTOR_" + KVLevel.Replace(" ", "") + "_" + Orientation, new Point(Bounds.Left + Offset.X, Bounds.Top + (Bounds.Height / 2) + Offset.Y - 2), PopupText);
                }
                else
                {
                    OutStream.WriteSymbolWithToolTip("REACTOR_" + KVLevel.Replace(" ", "") + "_" + Orientation, new Point(Bounds.Left + Offset.X, Bounds.Top + Offset.Y), PopupText);
                    OutStream.WriteSymbolWithToolTip("RESISTOR_" + KVLevel.Replace(" ", "") + "_" + Orientation, new Point(Bounds.Left + (Bounds.Width / 2) + Offset.X, Bounds.Top + Offset.Y), PopupText);
                }
            }
            else if (xElem.Name == "Capacitor")
            {
                String Orientation = xElem.Attributes["Orientation"].Value;
                if (Orientation == "Horizontal")
                    Orientation = "Right";
                else if (Orientation == "Vertical")
                    Orientation = "Up";
                //if (xElem.Attributes["Orientation"].Value == "Right")
                Bounds.Y -= 3;
                OutStream.WriteSymbolWithToolTip(xElem.Name.ToUpper() + "_" + KVLevel.Replace(" ", "") + "_" + Orientation.Substring(0, 1), new Point(Bounds.Left + Offset.X - 2, Offset.Y + Bounds.Top + 1), PopupText);
            }
            else if (xElem.Name == "Load" || xElem.Name.Equals("LAAR", StringComparison.CurrentCultureIgnoreCase) || xElem.Name == "Reactor")
                OutStream.WriteSymbolWithToolTip(xElem.Name.ToUpper() + "_" + KVLevel.Replace(" ", "") + "_" + xElem.Attributes["Orientation"].Value.Substring(0, 1), new Point(Bounds.Left + 1 + Offset.X, Offset.Y + Bounds.Top + 1), PopupText);
            else if (xElem.Name == "Line")
                OutStream.WriteDataBoundPictureWithToolTip(xElem.Name.ToUpper() + "_" + KVLevel.Replace(" ", "") + "_" + xElem.Attributes["Orientation"].Value.Substring(0, 1), PopupText, new Point(Bounds.Left + 1 + Offset.X, Offset.Y + Bounds.Top + 1), "MASEQP=" + Elem.TEID);
            else if (xElem.Name == "StaticVarCompensator")
                OutStream.WriteSymbolWithToolTip("SVC_" + KVLevel.Replace(" ", "") + "_" + xElem.Attributes["Orientation"].Value.Substring(0, 1), new Point(Bounds.Left + Offset.X - 5, Offset.Y + Bounds.Top - 5), PopupText);
            else if (xElem.Name == "EndCap")
                OutStream.WriteSymbolWithToolTip(xElem.Name.ToUpper() + "_" + KVLevel.Replace(" ", ""), new Point(Bounds.Left + 1 + Offset.X, Offset.Y + Bounds.Top - 3), PopupText);
            else if (xElem.Name == "Unit")
            {
                String UnitName = Elem.ParseSingle("Unit", null).Name;
                OutStream.SCADA.WriteDataBoundPictureWithToolTip("GEN_" + KVLevel.Replace(" ", "") + "_" + xElem.Attributes["Orientation"].Value.Substring(0, 1), PopupText, new Point(Bounds.Left + 1 + Offset.X, Bounds.Top + Offset.Y + 1), "PL=" + SubName, "UNIT=" + UnitName);
                OutStream.OTS.WriteDataBoundPictureWithToolTip("GEN_NET_" + KVLevel.Replace(" ", "") + "_" + xElem.Attributes["Orientation"].Value.Substring(0, 1), PopupText, new Point(Bounds.Left + 1 + Offset.X, Bounds.Top + Offset.Y + 1), "ST=" + SubName, "UN=" + UnitName);
                OutStream.NETMOM.WriteDataBoundPictureWithToolTip("GEN_NET_" + KVLevel.Replace(" ", "") + "_" + xElem.Attributes["Orientation"].Value.Substring(0, 1), PopupText, new Point(Bounds.Left + 1 + Offset.X, Bounds.Top + Offset.Y + 1), "ST=" + SubName, "UN=" + UnitName);
            }
            else if (xElem.Name == "Transformer")
            {
                //If we're a phase-shifting transformer, write accordingly                

                bool FirstWinding = true;
                foreach (XmlElement xWinding in xElem.SelectNodes("Winding"))
                    if ((!xWinding.HasAttribute("Visible") || XmlConvert.ToBoolean(xWinding.Attributes["Visible"].Value)) && xWinding.HasAttribute("rdfID"))
                    {
                        CIM_Element Winding = Model.FromRdfID(xWinding.Attributes["rdfID"].Value);
                        String WindingKVLevel = Winding.VoltageLevel == null ? "OTHERKV" : Data_Manager.LocateKVLevel(Winding.VoltageLevel.Name).Name.Replace(" ", "").ToUpper();

                        Rectangle Bounds2 = MM_Serializable<Rectangle>.ConvertObject(xWinding.Attributes["Bounds"].Value, null);
                        if (!FirstWinding && XmlConvert.ToBoolean(xWinding.ParentNode.Attributes["BaseElement.PhaseShifter"].Value))
                        {
                            //Depending on our orientation, shift the phase shifting transformer symbol appropriately
                            String Orientation = xWinding.Attributes["Orientation"].Value.Substring(0, 1);
                            if (Orientation == "D")
                                OutStream.WriteSymbolWithToolTip("TRANS_PHASE_" + WindingKVLevel + "_" + Orientation, new Point(Bounds.Left + Offset.X - 7, Bounds.Top + Offset.Y), PopupText);
                            else if (Orientation == "L")
                                OutStream.WriteSymbolWithToolTip("TRANS_PHASE_" + WindingKVLevel + "_" + Orientation, new Point(Bounds.Left + Offset.X, Bounds.Top + Offset.Y - 4), PopupText);
                            else if (Orientation == "R")
                                OutStream.WriteSymbolWithToolTip("TRANS_PHASE_" + WindingKVLevel + "_" + Orientation, new Point(Bounds.Left + Offset.X, Bounds.Top + Offset.Y - 7), PopupText);
                            else
                                OutStream.WriteSymbolWithToolTip("TRANS_PHASE_" + WindingKVLevel + "_" + Orientation, new Point(Bounds.Left + Offset.X, Bounds.Top + Offset.Y), PopupText);
                        }
                        else if (!xElem.HasAttribute("BaseElement.PhaseShifter") || !XmlConvert.ToBoolean(xElem.Attributes["BaseElement.PhaseShifter"].Value))
                            OutStream.WriteSymbolWithToolTip("TRANS_" + WindingKVLevel + "_" + xWinding.Attributes["Orientation"].Value.Substring(0, 1), new Point(Bounds.Left + Offset.X + Bounds2.X, Offset.Y + Bounds.Top + Bounds2.Y), PopupText);
                        FirstWinding = false;

                    }
            }
            else if (xElem.Name == "PricingVector")
                if (XmlConvert.ToBoolean(xElem.Attributes["IsPositive"].Value))
                    OutStream.WriteSymbolWithCamAndToolTip("PRICING_VECTOR_" + KVLevel.Replace(' ', '_') + "_" + xElem.Attributes["Orientation"].Value.Substring(0, 1), MM_Serializable<Rectangle>.ConvertObject(xElem.Attributes["Bounds"].Value, null).Location, Offset, "PV_SHOW_NETMOM", PopupText);
                else if (xElem.Attributes["Orientation"].Value[0] == 'U')
                    OutStream.WriteSymbolWithCamAndToolTip("PRICING_VECTOR_" + KVLevel.Replace(' ', '_') + "_D", MM_Serializable<Rectangle>.ConvertObject(xElem.Attributes["Bounds"].Value, null).Location, Offset, "PV_SHOW_NETMOM", PopupText);
                else if (xElem.Attributes["Orientation"].Value[0] == 'D')
                    OutStream.WriteSymbolWithCamAndToolTip("PRICING_VECTOR_" + KVLevel.Replace(' ', '_') + "_U", MM_Serializable<Rectangle>.ConvertObject(xElem.Attributes["Bounds"].Value, null).Location, Offset, "PV_SHOW_NETMOM", PopupText);
                else if (xElem.Attributes["Orientation"].Value[0] == 'L')
                    OutStream.WriteSymbolWithCamAndToolTip("PRICING_VECTOR_" + KVLevel.Replace(' ', '_') + "_R", MM_Serializable<Rectangle>.ConvertObject(xElem.Attributes["Bounds"].Value, null).Location, Offset, "PV_SHOW_NETMOM", PopupText);
                else if (xElem.Attributes["Orientation"].Value[0] == 'R')
                    OutStream.WriteSymbolWithCamAndToolTip("PRICING_VECTOR_" + KVLevel.Replace(' ', '_') + "_L", MM_Serializable<Rectangle>.ConvertObject(xElem.Attributes["Bounds"].Value, null).Location, Offset, "PV_SHOW_NETMOM", PopupText);
                else
                    throw new InvalidOperationException("Unknown vector orientation");
            else if (xElem.Name == "PokePoint")
            {
                if (xElem.HasAttribute("IsJumper") && XmlConvert.ToBoolean(xElem.Attributes["IsJumper"].Value))
                {
                    String Orientation = xElem.Attributes["Orientation"].Value;
                    Orientation = (Orientation == "Vertical" || Orientation == "Up" || Orientation == "Down") ? "V" : "H";
                    if (Orientation == "H")
                        OutStream.WriteSymbolWithToolTip("JUMPER_" + KVLevel.Replace(" ", "") + "_" + Orientation, MM_Serializable<Rectangle>.ConvertObject(xElem.Attributes["Bounds"].Value, null).Location, new Point(Offset.X - 1, Offset.Y - 4), PopupText);
                    else
                        OutStream.WriteSymbolWithToolTip("JUMPER_" + KVLevel.Replace(" ", "") + "_" + Orientation, MM_Serializable<Rectangle>.ConvertObject(xElem.Attributes["Bounds"].Value, null).Location, new Point(Offset.X - 1, Offset.Y), PopupText);
                }
                else if (xElem.HasAttribute("IsVisible") && XmlConvert.ToBoolean(xElem.Attributes["IsVisible"].Value))
                {
                    Rectangle Rect = MM_Serializable<Rectangle>.ConvertObject(xElem.Attributes["Bounds"].Value, null);
                    if (Rect.Width <= 8 && Rect.Height <= 8 && xElem["Descriptor"] == null && xElem["SecondaryDescriptor"] == null)
                        OutStream.WriteRectangleWithCam(Rect, Offset, KVLevel.Replace(' ', '_'), "NODE_SHOW_NETMOM");
                    else
                        OutStream.WriteRectangle(Rect, Offset, KVLevel.Replace(' ', '_'));
                }
            }
            else if (xElem.Name == "Node")
            {
                //Find our other one-line
                CIM_Element OtherBase = null;
                CIM_Trace[] Traces;
                if (BaseElement is CIM_Trace)
                    OtherBase = Elem.Substation;
                else if (Model.TraceLinkages.TryGetValue(Elem.rdfID, out Traces))
                    foreach (CIM_Trace OtherTrace in Traces)
                        if (!OtherTrace.Name.StartsWith("D"))
                            OtherBase = OtherTrace;

                String OtherBaseStr = "";
                if (OtherBase is CIM_Trace)
                    OtherBaseStr = "NAV_B2B_" + OtherBase.Name.ToUpper();
                else if (OtherBase is CIM_Element)
                    OtherBaseStr = "NAV_SUB_" + OtherBase.Name.ToUpper();

                if (Bounds.Width <= 8 && Bounds.Height <= 8 && xElem["Descriptor"] == null && xElem["SecondaryDescriptor"] == null)
                    OutStream.WriteRectangleWithCamAndKeysetAndToolTip(Bounds, Offset, KVLevel.Replace(' ', '_'), "NODE_SHOW_NETMOM", OtherBaseStr, PopupText);
                else
                    OutStream.WriteRectangleWithKeysetAndToolTip(Bounds, Offset, KVLevel.Replace(' ', '_'), OtherBaseStr, PopupText);
            }
            else if (xElem.Name == "Winding")
            { }
            else
                throw new InvalidOperationException("Unknown element: " + xElem.Name);            
          }

        /// <summary>
        /// Write out the image and connectivity of a node
        /// </summary>
        /// <param name="OutStream"></param>
        /// <param name="OneLine"></param>
        /// <param name="Model"></param>
        /// <param name="SCADA"></param>
        /// <param name="BaseElement"></param>
        /// <param name="xNode"></param>
        /// <param name="Synchroscopes"></param>
        private static void WriteNode(MM_DDL_Exporter_Stream OutStream, MM_Database_OneLine OneLine, MM_Database_Model Model, bool SCADA, CIM_Element BaseElement, XmlElement xNode, Dictionary<CIM_RdfID, bool> Synchroscopes)
        {
            CIM_Element Node;
            if (xNode.HasAttribute("rdfID"))
                Node = BaseElement.CIM.FromRdfID(new CIM_RdfID(xNode.Attributes["rdfID"].Value));
            else
                Node = BaseElement.CIM.FromTEID(XmlConvert.ToUInt64(xNode.Attributes["BaseElement.TEID"].Value));

            String KVLevel = Data_Manager.LocateKVLevel(Node.VoltageLevel.Name).Name.Replace(' ','_').ToUpper(); 
            Rectangle NodeBounds = (Rectangle)MM_Serializable.ConvertValue(typeof(Rectangle), xNode.Attributes["Bounds"].Value, null, BaseElement.CIM.Repository);

            //Go through each target element hand handle accordingly
            foreach (XmlElement xCh in xNode.ChildNodes)
                if (xCh.Name != "Descriptor" && xCh.Name != "SecondaryDescriptor")
                {
                    //Find our target element
                    XmlElement xNodeTarget = null;
                    if (xCh.HasAttribute("rdfID"))
                        xNodeTarget = xCh.OwnerDocument.SelectSingleNode("//*[@rdfID='" + xCh.Attributes["rdfID"].Value + "']") as XmlElement;
                    if (xNodeTarget == null)
                        xNodeTarget = xCh.OwnerDocument.SelectSingleNode("//*[@BaseElement.TEID='" + xCh.Attributes["TEID"].Value + "']") as XmlElement;

                    //Draw our node paths
                    using (GraphicsPath FoundPath = MM_OneLine_Element.BuildNodePath(xNode, xCh))
                        foreach (KeyValuePair<PointF, PointF> kvp in MM_HitTesting.ExtractLines(FoundPath))
                        {
                            Point pt1 = Point.Round(new PointF(kvp.Key.X + Offset.X, kvp.Key.Y + Offset.Y));
                            Point pt2 = Point.Round(new PointF(kvp.Value.X + Offset.X, kvp.Value.Y + Offset.Y));
                            OutStream.WriteGraphicLine(pt1, pt2, false, KVLevel);
                        }

                    foreach (XmlElement xCh2 in xCh.ChildNodes)
                    {
                        //Now, if we have a visible poke point, jumper, or pricing vector, handle it.
                        if (xCh2.Name == "PokePoint")
                        {
                            // if (XmlConvert.ToBoolean(xCh2.Attributes["IsVisible"].Value))
                            if (xCh2.HasAttribute("IsJumper") && XmlConvert.ToBoolean(xCh2.Attributes["IsJumper"].Value))
                            {
                                String Orientation = xCh2.Attributes["Orientation"].Value;
                                Orientation = (Orientation == "Vertical" || Orientation == "Up" || Orientation == "Down") ? "V" : "H";
                                if (Orientation == "H")
                                    OutStream.WriteSymbol("JUMPER_" + KVLevel.Replace("_", "") + "_" + Orientation, MM_Serializable<Rectangle>.ConvertObject(xCh2.Attributes["Bounds"].Value, null).Location, new Point(Offset.X - 1, Offset.Y - 4));
                                else
                                    OutStream.WriteSymbol("JUMPER_" + KVLevel.Replace("_", "") + "_" + Orientation, MM_Serializable<Rectangle>.ConvertObject(xCh2.Attributes["Bounds"].Value, null).Location, new Point(Offset.X - 1, Offset.Y));
                            }
                            else if (xCh2.HasAttribute("IsVisible") && XmlConvert.ToBoolean(xCh2.Attributes["IsVisible"].Value))
                            {
                                Rectangle Rect = MM_Serializable<Rectangle>.ConvertObject(xCh2.Attributes["Bounds"].Value, null);
                                if (Rect.Width <= 8 && Rect.Height <= 8 && xCh2["Descriptor"] == null && xCh2["SecondaryDescriptor"] == null)
                                    OutStream.WriteRectangleWithCam(Rect, Offset, KVLevel, "NODE_SHOW_NETMOM");
                                else
                                    OutStream.WriteRectangle(Rect, Offset, KVLevel);
                            }
                        }
                        else if (xCh2.Name == "PricingVector")
                            if (XmlConvert.ToBoolean(xCh2.Attributes["IsPositive"].Value))
                                OutStream.WriteSymbolWithCam("PRICING_VECTOR_" + KVLevel + "_" + xCh2.Attributes["Orientation"].Value.Substring(0, 1), MM_Serializable<Rectangle>.ConvertObject(xCh2.Attributes["Bounds"].Value, null).Location, Offset, "PV_SHOW_NETMOM");
                            else if (xCh2.Attributes["Orientation"].Value[0] == 'U')
                                OutStream.WriteSymbolWithCam("PRICING_VECTOR_" + KVLevel + "_D", MM_Serializable<Rectangle>.ConvertObject(xCh2.Attributes["Bounds"].Value, null).Location, Offset, "PV_SHOW_NETMOM");
                            else if (xCh2.Attributes["Orientation"].Value[0] == 'D')
                                OutStream.WriteSymbolWithCam("PRICING_VECTOR_" + KVLevel + "_U", MM_Serializable<Rectangle>.ConvertObject(xCh2.Attributes["Bounds"].Value, null).Location, Offset, "PV_SHOW_NETMOM");
                            else if (xCh2.Attributes["Orientation"].Value[0] == 'L')
                                OutStream.WriteSymbolWithCam("PRICING_VECTOR_" + KVLevel + "_R", MM_Serializable<Rectangle>.ConvertObject(xCh2.Attributes["Bounds"].Value, null).Location, Offset, "PV_SHOW_NETMOM");
                            else if (xCh2.Attributes["Orientation"].Value[0] == 'R')
                                OutStream.WriteSymbolWithCam("PRICING_VECTOR_" + KVLevel + "_L", MM_Serializable<Rectangle>.ConvertObject(xCh2.Attributes["Bounds"].Value, null).Location, Offset, "PV_SHOW_NETMOM");
                            else
                                throw new InvalidOperationException("Unknown vector orientation");
                        else
                            throw new InvalidOperationException("Unknown item error");
                        /*
                        //Check for labels
                        if (xCh2["Descriptor"] != null)
                            if (OldLabel)
                                WriteFGLabel(OutStream, OneLine, Model, SCADA, xCh2["Descriptor"], BaseElement, Synchroscopes);
                            else
                                WriteLabel(OutStream, OneLine, Model, SCADA, xCh2["Descriptor"], BaseElement);
                        if (xCh2["SecondaryDescriptor"] != null)
                            if (OldLabel)
                                WriteFGLabel(OutStream, OneLine, Model, SCADA, xCh2["SecondaryDescriptor"], BaseElement, Synchroscopes);
                            else
                                WriteLabel(OutStream, OneLine, Model, SCADA, xCh2["SecondaryDescriptor"], BaseElement);
                        */
                    }

                    //Now, draw a line to our node
                    XmlElement TargetElemXML = xNode.OwnerDocument.DocumentElement.SelectSingleNode("Elements/" + xCh.Name + "[@BaseElement.TEID='" + xCh.Attributes["TEID"].Value + "']") as XmlElement;
                    Rectangle TargetElement = MM_Serializable<Rectangle>.ConvertObject(TargetElemXML.Attributes["Bounds"].Value, null);
                }                    

            //We have a rectangular node, so we have to set up things to work properly.
            {
                //Find our other one-line
                CIM_Element OtherBase = null;                
                if (BaseElement is CIM_Trace)
                    OtherBase = Node.Substation;
                else
                    foreach (CIM_Trace OtherTrace in Model.TraceLinkages[Node.rdfID])
                        if (!OtherTrace.Name.StartsWith("D"))                    
                            OtherBase = OtherTrace;
                
                String OtherBaseStr="";
                if (OtherBase is CIM_Trace)
                    OtherBaseStr = "NAV_B2B_" + OtherBase.Name.ToUpper();
                else if (OtherBase is CIM_Element)
                    OtherBaseStr = "NAV_SUB_" + OtherBase.Name.ToUpper();

                Rectangle Rect = MM_Serializable<Rectangle>.ConvertObject(xNode.Attributes["Bounds"].Value, null);                
                if (Rect.Width <= 8 && Rect.Height <= 8 && xNode["Descriptor"] == null && xNode["SecondaryDescriptor"] == null)
                    OutStream.WriteRectangleWithCamAndKeyset(Rect, Offset, KVLevel, "NODE_SHOW_NETMOM", OtherBaseStr);
                else
                    OutStream.WriteRectangleWithKeyset(Rect, Offset, KVLevel, OtherBaseStr);
            }

            //Write out our labels
           /* if (xNode["Descriptor"] != null)
                if (OldLabel)
                    WriteFGLabel(OutStream, OneLine, Model, SCADA, xNode["Descriptor"], BaseElement, Synchroscopes);
                else
                    WriteLabel(OutStream, OneLine, Model, SCADA, xNode["Descriptor"], BaseElement);
            if (xNode["SecondaryDescriptor"] != null)
                if (OldLabel)
                    WriteFGLabel(OutStream, OneLine, Model, SCADA, xNode["SecondaryDescriptor"], BaseElement, Synchroscopes);
                else
                    WriteLabel(OutStream, OneLine, Model, SCADA, xNode["SecondaryDescriptor"], BaseElement);
            */
        }

        /// <summary>
        /// Determine the paths between elements
        /// </summary>
        /// <param name="xNode">The XML configuration for the node</param>
        /// <param name="BaseBounds">The bounds of the node</param>
        /// <returns></returns>
        private static List<GraphicsPath> DerivePaths(XmlElement xNode, Rectangle BaseBounds)
        {
            List<GraphicsPath> OutPath = new List<GraphicsPath>();            
            foreach (XmlElement xElem in xNode.ChildNodes)
                if (xElem.Name != "Descriptor" && xElem.Name != "SecondaryDescriptor")
                {
                    XmlElement LastElement = xElem;
                    GraphicsPath NewPath = new GraphicsPath();
                    foreach (XmlElement xCh in xElem.ChildNodes)
                        AddStraightLine(NewPath, LastElement, LastElement = xCh);
                    AddStraightLine(NewPath, LastElement, xNode);
                    OutPath.Add(NewPath);
                }
            return OutPath;
        }

        /// <summary>
        /// Retrieve a straight line
        /// </summary>
        /// <param name="FromRect"></param>
        /// <param name="ToRect"></param>
        /// <param name="Center1"></param>
        /// <param name="Center2"></param>
        /// <param name="MaintainCenter1"></param>
        /// <param name="MaintainCenter2"></param>
        public static void GetStraightLine(Rectangle FromRect, Rectangle ToRect, out Point Center1, out Point Center2, bool MaintainCenter1, bool MaintainCenter2)
        {
            Center1 = MM_Converter.CenterRect(FromRect);
            Center2 = MM_Converter.CenterRect(ToRect);

            if (Center2.X >= FromRect.Left && Center2.X <= FromRect.Right)
                Center1.X = Center2.X;
            else if (Center2.X > FromRect.Right)
                Center1.X = FromRect.Right;
            else if (Center2.X < FromRect.Left)
                Center1.X = FromRect.Left;
            if (Center2.Y >= FromRect.Top && Center2.Y <= FromRect.Bottom)
                Center1.Y = Center2.Y;
            else if (Center2.Y < FromRect.Top)
                Center1.Y = FromRect.Top;
            else if (Center2.Y > FromRect.Bottom)
                Center1.Y = FromRect.Bottom;

            if (Center1.X >= ToRect.Left && Center1.X <= ToRect.Right)
                Center2.X = Center1.X;
            else if (Center1.X > ToRect.Right)
                Center2.X = ToRect.Right;
            else if (Center1.X < ToRect.Left)
                Center2.X = ToRect.Left;

            if (Center1.Y >= ToRect.Top && Center1.Y <= ToRect.Bottom)
                Center2.Y = Center1.Y;
            else if (Center1.Y < ToRect.Top)
                Center2.Y = ToRect.Top;
            else if (Center1.Y > ToRect.Bottom)
                Center2.Y = ToRect.Bottom;

            //Shift our points out to the edge
            if (Center1.X == Center2.X && Center1.Y < Center2.Y)
            {
                Center1.Y = MaintainCenter1 ? MM_Converter.CenterRect(FromRect).Y : FromRect.Bottom- 2;
                Center2.Y = MaintainCenter2 ? MM_Converter.CenterRect(ToRect).Y : ToRect.Top + 2;  
            }
            else if (Center1.X == Center2.X && Center1.Y > Center2.Y)
            {
                Center1.Y = MaintainCenter2 ? MM_Converter.CenterRect(ToRect).Y : ToRect.Bottom - 2;
                Center2.Y = MaintainCenter1 ? MM_Converter.CenterRect(FromRect).Y : FromRect.Top + 2;                    
            }
            else if (Center1.Y == Center2.Y && Center1.X < Center2.X)
            {
                Center1.X = MaintainCenter1 ? MM_Converter.CenterRect(FromRect).X : FromRect.Right - 2;
                Center2.X = MaintainCenter2 ? MM_Converter.CenterRect(ToRect).X : ToRect.Left + 2;                
            }
            else if (Center1.Y == Center2.Y && Center1.X > Center2.X)
            {
                Center1.X = MaintainCenter2 ? MM_Converter.CenterRect(ToRect).X : ToRect.Right - 2;
                Center2.X = MaintainCenter1 ? MM_Converter.CenterRect(FromRect).X : FromRect.Left + 2;                

               
            }

       
        }

        /// <summary>
        /// Add a straight line between to rectangles
        /// </summary>
        /// <param name="gp"></param>
        /// <param name="FromElement"></param>
        /// <param name="ToElement"></param>
        private static void AddStraightLine(GraphicsPath gp, XmlElement FromElement, XmlElement ToElement)
        {

            //Determine our original centers
            Rectangle FromRect = Rectangle.Empty, ToRect = Rectangle.Empty;


            if (FromElement.Attributes.Count == 1)
            {
                foreach (XmlElement xCh in FromElement.SelectNodes("/One_Line/Elements/" + FromElement.Name))
                    if (xCh.Attributes["BaseElement.TEID"].Value == FromElement.Attributes["TEID"].Value)
                        FromRect = MM_Serializable<Rectangle>.ConvertObject(xCh.Attributes["Bounds"].Value, null);
            }
            else
                FromRect = MM_Serializable<Rectangle>.ConvertObject(FromElement.Attributes["Bounds"].Value, null);
            if (ToElement.Attributes.Count == 1)
            {
                foreach (XmlElement xCh in ToElement.SelectNodes("/One_Line/Elements/" + FromElement.Name))
                    if (xCh.Attributes["BaseElement.TEID"].Value == ToElement.Attributes["TEID"].Value)
                        ToRect = MM_Serializable<Rectangle>.ConvertObject(xCh.Attributes["Bounds"].Value, null);
            }
            else
                ToRect = MM_Serializable<Rectangle>.ConvertObject(ToElement.Attributes["Bounds"].Value, null);
            Point Center1 = MM_Converter.CenterRect(FromRect);
            Point Center2 = MM_Converter.CenterRect(ToRect);


            //Adjust the first point if need be

            if (FromElement.Name == "Node" || FromElement.Name == "PokePoint")
            {
                if (Center2.X >= FromRect.Left && Center2.X <= FromRect.Right)
                    Center1.X = Center2.X;
                else if (Center2.X > FromRect.Right)
                    Center1.X = FromRect.Right;
                else if (Center2.X < FromRect.Left)
                    Center1.X = FromRect.Left;


                if (Center2.Y >= FromRect.Top && Center2.Y <= FromRect.Bottom)
                    Center1.Y = Center2.Y;
                else if (Center2.Y < FromRect.Top)
                    Center1.Y = FromRect.Top;
                else if (Center2.Y > FromRect.Bottom)
                    Center1.Y = FromRect.Bottom;
            }

            //Adjust the second point if need be
            if (ToElement.Name == "Node" || ToElement.Name == "PokePoint")
            {
                if (Center1.X >= ToRect.Left && Center1.X <= ToRect.Right)
                    Center2.X = Center1.X;
                else if (Center1.X > ToRect.Right)
                    Center2.X = ToRect.Right;
                else if (Center1.X < ToRect.Left)
                    Center2.X = ToRect.Left;

                if (Center1.Y >= ToRect.Top && Center1.Y <= ToRect.Bottom)
                    Center2.Y = Center1.Y;
                else if (Center1.Y < ToRect.Top)
                    Center2.Y = ToRect.Top;
                else if (Center1.Y > ToRect.Bottom)
                    Center2.Y = ToRect.Bottom;
            }

            //Shift our points out to the edge
            if (Center1.X == Center2.X && Center1.Y < Center2.Y)
            {
                Center1.Y = FromRect.Bottom-2;
                Center2.Y = ToRect.Top+2;
            }
            else if (Center1.X == Center2.X && Center1.Y > Center2.Y)
            {
                Center1.Y = ToRect.Bottom-2;
                Center2.Y = FromRect.Top+2;
            }

            else if (Center1.Y == Center2.Y && Center1.X < Center2.X)
            {
                Center1.X = FromRect.Right-2;
                Center2.X = ToRect.Left+2;
            }
            else if (Center1.Y == Center2.Y && Center1.X > Center2.X)
            {
                Center1.X = ToRect.Right-2;
                Center2.X = FromRect.Left+2;
            }


            gp.AddLine(Center1, Center2);
        }

      
        #endregion

        /// <summary>
        /// Use FGBuilder to compile a one-line, and save its results to a particular path.
        /// </summary>
        /// <param name="DDLPath"></param>
        /// <param name="CompiledDirectory"></param>
        /// <param name="ImageDirectory"></param>
        /// <param name="EMSDirectory"></param>
        public static void CompileOneLine(string DDLPath, string CompiledDirectory, string ImageDirectory, string EMSDirectory)
        {
            Process OutProcess = new Process();
            OutProcess.StartInfo.WorkingDirectory = EMSDirectory;
            OutProcess.StartInfo.FileName = "fgb_builder_app.exe";
            OutProcess.StartInfo.Arguments = String.Format("-compile -ddlDir \"{0}\" -outDir \"{1}\" -imageDir \"{2}\" -version \"WebFG 3.5.0\" -notembedimage -DSET \"{3}\"", Path.GetDirectoryName(DDLPath), CompiledDirectory, ImageDirectory, DDLPath);
            OutProcess.Start();
            OutProcess.WaitForExit();            
        }

        /// <summary>
        /// Compile a one-line, and see whehter it did so properly.
        /// </summary>
        /// <param name="DDLDirectory"></param>
        /// <param name="OneLineName"></param>
        /// <param name="CompiledDirectory"></param>
        /// <param name="ImageDirectory"></param>
        /// <param name="EMSDirectory"></param>
        /// <returns></returns>
        public static Exception CompileOneLineWithConfirmation(String DDLDirectory, String OneLineName, String CompiledDirectory, String ImageDirectory, String EMSDirectory)
        {

            //Compile our one-lines
            MM_DDL_Exporter.CompileOneLine(Path.Combine(DDLDirectory, "SCADA-" + OneLineName + "_dset.ddl"), CompiledDirectory, ImageDirectory, EMSDirectory);
            MM_DDL_Exporter.CompileOneLine(Path.Combine(DDLDirectory, "NETMOM-" + OneLineName + "_dset.ddl"), CompiledDirectory, ImageDirectory, EMSDirectory);
            MM_DDL_Exporter.CompileOneLine(Path.Combine(DDLDirectory, "OTS-" + OneLineName + "_dset.ddl"), CompiledDirectory, ImageDirectory, EMSDirectory);


            //Read our log files
            StringBuilder OutSb = new StringBuilder();
            FileInfo Netmom = new FileInfo(Path.Combine(CompiledDirectory, "DISPLAY_SET_NETMOM-" + OneLineName + ".TXT"));
            FileInfo Ots = new FileInfo(Path.Combine(CompiledDirectory, "DISPLAY_SET_OTS-" + OneLineName + ".TXT"));
            FileInfo Scada = new FileInfo(Path.Combine(CompiledDirectory, "DISPLAY_SET_SCADA-" + OneLineName + ".TXT"));
            if (Netmom.Exists)
                using (StreamReader sRd = Netmom.OpenText())
                    OutSb.AppendLine(sRd.ReadToEnd());
            if (Ots.Exists)
                using (StreamReader sRd = Ots.OpenText())
                    OutSb.AppendLine(sRd.ReadToEnd());
            if (Scada.Exists)
                using (StreamReader sRd = Scada.OpenText())
                    OutSb.AppendLine(sRd.ReadToEnd());

            if (OutSb.ToString().Contains(" E:"))
                return new Exception("Error compiling: " + OutSb.ToString());
            else if (!File.Exists(CompiledDirectory + "\\RTNet\\" + OneLineName + ".fgdisplay"))
                return new Exception("Unable to find compiled RTNET display");
            else if (!File.Exists(CompiledDirectory + "\\scada\\" + OneLineName + ".fgdisplay"))
                return new Exception("Unable to find compiled SCADA display");
            else if (!File.Exists(CompiledDirectory + "\\dtspsm\\" + OneLineName + ".fgdisplay"))
                return new Exception("Unable to find compiled OTS display");
            else
                return null;
        }
    }
}
